mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge branch 'the1812:preview-features' into preview-features
This commit is contained in:
commit
c4c1c1c2ec
@ -45,6 +45,7 @@ $prefix: 'simplifyComments-switch';
|
|||||||
.reply-info,
|
.reply-info,
|
||||||
.sub-reply-info {
|
.sub-reply-info {
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
|
flex-basis: 100% !important;
|
||||||
}
|
}
|
||||||
.reply-time,
|
.reply-time,
|
||||||
.sub-reply-time {
|
.sub-reply-time {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export interface KeyBindingActionContext {
|
|||||||
}
|
}
|
||||||
export interface KeyBindingAction {
|
export interface KeyBindingAction {
|
||||||
displayName: string
|
displayName: string
|
||||||
run: (context: KeyBindingActionContext) => any
|
run: (context: KeyBindingActionContext) => unknown
|
||||||
prevent?: boolean
|
prevent?: boolean
|
||||||
ignoreTyping?: boolean
|
ignoreTyping?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,6 +74,13 @@ const entry: ComponentEntry = async ({ settings, metadata }) => {
|
|||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
addComponentListener(
|
||||||
|
`${metadata.name}.showPlaceholder`,
|
||||||
|
(value: boolean) => {
|
||||||
|
document.body.classList.toggle('promotion-show-placeholder', value)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
export const component = defineComponentMetadata({
|
export const component = defineComponentMetadata({
|
||||||
name: 'removePromotions',
|
name: 'removePromotions',
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import 'common';
|
||||||
|
|
||||||
#slide_ad,
|
#slide_ad,
|
||||||
.v-wrap .vcd,
|
.v-wrap .vcd,
|
||||||
.ad-report,
|
.ad-report,
|
||||||
@ -80,3 +82,25 @@ body.dark .blocked-ads.new {
|
|||||||
body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-goods) {
|
body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-goods) {
|
||||||
margin: 0 !important;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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()
|
|
||||||
})()
|
|
||||||
104
src/components/video/player-agent/bangumi.ts
Normal file
104
src/components/video/player-agent/bangumi.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/components/video/player-agent/base.ts
Normal file
97
src/components/video/player-agent/base.ts
Normal 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
|
||||||
|
}
|
||||||
10
src/components/video/player-agent/index.ts
Normal file
10
src/components/video/player-agent/index.ts
Normal 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()
|
||||||
|
})()
|
||||||
0
src/components/video/player-agent/live-player.ts
Normal file
0
src/components/video/player-agent/live-player.ts
Normal file
67
src/components/video/player-agent/types.ts
Normal file
67
src/components/video/player-agent/types.ts
Normal 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
|
||||||
|
}
|
||||||
83
src/components/video/player-agent/video-player-mixed.ts
Normal file
83
src/components/video/player-agent/video-player-mixed.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/components/video/player-agent/video-player-v2.ts
Normal file
141
src/components/video/player-agent/video-player-v2.ts
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user