Merge branch 'the1812:preview-features' into preview-features

This commit is contained in:
Tinhone 2022-12-15 09:07:55 +08:00 committed by GitHub
commit c4c1c1c2ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 535 additions and 481 deletions

View File

@ -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 {

View File

@ -13,7 +13,7 @@ export interface KeyBindingActionContext {
}
export interface KeyBindingAction {
displayName: string
run: (context: KeyBindingActionContext) => any
run: (context: KeyBindingActionContext) => unknown
prevent?: boolean
ignoreTyping?: boolean
}

View File

@ -74,6 +74,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',

View File

@ -1,3 +1,5 @@
@import 'common';
#slide_ad,
.v-wrap .vcd,
.ad-report,
@ -80,3 +82,25 @@ body.dark .blocked-ads.new {
body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-goods) {
margin: 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;
}
}
}

View File

@ -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<Target = HTMLElement> = {
(): Promise<Target | null>
sync: () => Target | null
selector: string
}
interface CustomNestedQuery<QueryResult> {
[key: string]: QueryResult | CustomNestedQuery<QueryResult>
}
interface CustomQuery<QueryResult> extends CustomNestedQuery<QueryResult> {
[key: string]: QueryResult
}
type AgentType = 'video' | 'bangumi'
type CustomQueryProvider<TargetType> = {
[key in AgentType]?: TargetType
} & { video: TargetType }
interface PlayerQuery<QueryResult> extends CustomNestedQuery<QueryResult> {
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<HTMLElement>(selector)
func.selector = selector
func.sync = () => dq(selector) as HTMLElement
return func
}
const selectorWrap = (query: CustomNestedQuery<string>): CustomNestedQuery<ElementQuery> => {
const map = (value: string | Record<string, any>): ElementQuery | Record<string, any> => {
if (typeof value !== 'string') {
return lodash.mapValues(value, map)
}
return elementQuery(value)
}
return lodash.mapValues(query, map) as CustomNestedQuery<ElementQuery>
}
const click = (target: ElementQuery) => {
const button = target.sync()
button?.click()
return button
}
export abstract class PlayerAgent {
abstract type: AgentType
abstract query: PlayerQuery<ElementQuery>
provideCustomQuery<CustomQueryType extends CustomQuery<string>>(
config: CustomQueryProvider<CustomQueryType>,
) {
const custom = selectorWrap(config[this.type] ?? config.video) as CustomQuery<ElementQuery>
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<ElementQuery>
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<HTMLElement>(bwpSelector)
}
return select<HTMLElement>(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<ElementQuery>
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<ElementQuery>
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()
})()

View File

@ -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<ElementQuery>
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()
}
}

View File

@ -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<HTMLElement>(selector)
func.selector = selector
func.sync = () => dq(selector) as HTMLElement
return func
}
export const selectorWrap = (query: CustomNestedQuery<string>): CustomNestedQuery<ElementQuery> => {
const map = (value: string | Record<string, any>): ElementQuery | Record<string, any> => {
if (typeof value !== 'string') {
return lodash.mapValues(value, map)
}
return elementQuery(value)
}
return lodash.mapValues(query, map) as CustomNestedQuery<ElementQuery>
}
export const click = (target: ElementQuery) => {
const button = target.sync()
button?.click()
return button
}
export abstract class PlayerAgent {
abstract type: AgentType
abstract query: PlayerQuery<ElementQuery>
provideCustomQuery<CustomQueryType extends CustomQuery<string>>(
config: CustomQueryProvider<CustomQueryType>,
) {
const custom = selectorWrap(config[this.type] ?? config.video) as CustomQuery<ElementQuery>
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
}

View File

@ -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()
})()

View File

@ -0,0 +1,67 @@
/** 元素查询函数, 调用时执行 `SpinQuery.select` 查询, 可访问 `selector` 获取选择器 */
export type ElementQuery<Target = HTMLElement> = {
(): Promise<Target | null>
sync: () => Target | null
selector: string
}
export interface CustomNestedQuery<QueryResult> {
[key: string]: QueryResult | CustomNestedQuery<QueryResult>
}
export interface CustomQuery<QueryResult> extends CustomNestedQuery<QueryResult> {
[key: string]: QueryResult
}
export type AgentType = 'video' | 'bangumi' | 'live'
export type CustomQueryProvider<TargetType> = {
[key in AgentType]?: TargetType
} & { video: TargetType }
export interface PlayerQuery<QueryResult> extends CustomNestedQuery<QueryResult> {
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
}

View File

@ -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<ElementQuery>
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()
}
}

View File

@ -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<ElementQuery>
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<HTMLElement>(bwpSelector)
}
return select<HTMLElement>(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()
}
}