mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
refactor: 重构视频缩放组件并添加新功能
- 重构代码结构,将功能拆分为多个模块 - 实现自定义缩放范围的配置 - 改进提示显示功能,增加持续时间设置 - 添加错误处理机制和清理函数
This commit is contained in:
parent
c7b3e79b53
commit
894eb2823e
@ -0,0 +1,43 @@
|
||||
import { ScalePreset } from './types'
|
||||
|
||||
// 缩放比例预设选项数组
|
||||
export const SCALE_PRESETS = [
|
||||
'50%',
|
||||
'75%',
|
||||
'100%',
|
||||
'110%',
|
||||
'125%',
|
||||
'150%',
|
||||
'200%',
|
||||
'自定义',
|
||||
] as const
|
||||
|
||||
// 缩放比例映射表
|
||||
export const SCALE_MAPPING: Record<ScalePreset, number> = {
|
||||
'50%': 0.5,
|
||||
'75%': 0.75,
|
||||
'100%': 1.0,
|
||||
'110%': 1.1,
|
||||
'125%': 1.25,
|
||||
'150%': 1.5,
|
||||
'200%': 2.0,
|
||||
自定义: 1.0,
|
||||
}
|
||||
|
||||
// 自定义缩放的范围设置
|
||||
export const CUSTOM_SCALE_CONFIG = {
|
||||
min: 50, // 50%
|
||||
max: 300, // 300%
|
||||
step: 10, // 步长为10%
|
||||
}
|
||||
|
||||
// Toast显示时间配置
|
||||
export const TOAST_DURATION_CONFIG = {
|
||||
defaultValue: 3.0, // 默认显示3秒
|
||||
min: 0.5, // 最小0.5秒
|
||||
max: 5.0, // 最大5秒
|
||||
step: 0.5, // 步长0.5秒
|
||||
}
|
||||
|
||||
// Toast显示类名
|
||||
export const TOAST_CLASS_NAME = 'be-video-scale-toast'
|
||||
@ -1,17 +1,36 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { playerAgent } from '@/components/video/player-agent'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { componentsTags } from '@/components/component'
|
||||
import { videoChange } from '@/core/observer'
|
||||
import { ScaleState, applyScale, updateScaleFromSettings } from './scale-service'
|
||||
import { showScaleToast, cleanupToasts, handleError } from './ui-utils'
|
||||
import { CUSTOM_SCALE_CONFIG, SCALE_PRESETS, TOAST_DURATION_CONFIG } from './constants'
|
||||
import { ScalePreset } from './types'
|
||||
import './styles.scss'
|
||||
|
||||
// 定义缩放预设选项类型
|
||||
type ScalePreset = '1.0x' | '1.25x' | '1.5x' | '2.0x' | 'custom'
|
||||
// 创建选项元数据对象,用于动态修改
|
||||
const customScaleOption = {
|
||||
defaultValue: 100, // 100%
|
||||
displayName: '自定义缩放比 (%)',
|
||||
slider: {
|
||||
min: CUSTOM_SCALE_CONFIG.min,
|
||||
max: CUSTOM_SCALE_CONFIG.max,
|
||||
step: CUSTOM_SCALE_CONFIG.step,
|
||||
},
|
||||
formatValue: (value: number) => value,
|
||||
hidden: true,
|
||||
}
|
||||
|
||||
const SCALE_MAPPING: Record<ScalePreset, number> = {
|
||||
'1.0x': 1.0,
|
||||
'1.25x': 1.25,
|
||||
'1.5x': 1.5,
|
||||
'2.0x': 2.0,
|
||||
custom: 1.0,
|
||||
// 创建toastDuration选项元数据对象
|
||||
const toastDurationOption = {
|
||||
defaultValue: TOAST_DURATION_CONFIG.defaultValue,
|
||||
displayName: '提示显示时间 (秒)',
|
||||
slider: {
|
||||
min: TOAST_DURATION_CONFIG.min,
|
||||
max: TOAST_DURATION_CONFIG.max,
|
||||
step: TOAST_DURATION_CONFIG.step,
|
||||
},
|
||||
hidden: false, // 默认显示
|
||||
}
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
@ -21,127 +40,112 @@ export const component = defineComponentMetadata({
|
||||
tags: [componentsTags.video],
|
||||
options: {
|
||||
scalePreset: {
|
||||
defaultValue: '1.0x' as ScalePreset,
|
||||
defaultValue: '100%' as ScalePreset,
|
||||
displayName: '缩放比例预设',
|
||||
dropdownEnum: ['1.0x', '1.25x', '1.5x', '2.0x', 'custom'],
|
||||
dropdownEnum: [...SCALE_PRESETS],
|
||||
},
|
||||
customScale: {
|
||||
defaultValue: 1.0,
|
||||
displayName: '自定义缩放比例',
|
||||
slider: {
|
||||
min: 0.5,
|
||||
max: 3.0,
|
||||
step: 0.1,
|
||||
},
|
||||
customScale: customScaleOption,
|
||||
showToast: {
|
||||
defaultValue: true,
|
||||
displayName: '显示缩放提示',
|
||||
},
|
||||
toastDuration: toastDurationOption,
|
||||
},
|
||||
entry: ({ settings }) => {
|
||||
// 当前缩放比例
|
||||
let currentScale = 1.0
|
||||
const maxScale = 3.0
|
||||
const minScale = 0.5
|
||||
entry: async ({ settings }) => {
|
||||
// 缩放状态管理
|
||||
const scaleState = new ScaleState()
|
||||
|
||||
// 显示缩放比例提示
|
||||
const showScaleToast = (scale: number) => {
|
||||
try {
|
||||
// 创建一个临时的toast元素显示缩放比例
|
||||
let toast = document.querySelector('.be-video-scale-toast') as HTMLDivElement
|
||||
if (!toast) {
|
||||
toast = document.createElement('div')
|
||||
toast.className = 'be-video-scale-toast'
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
z-index: 9999;
|
||||
font-size: 16px;
|
||||
pointer-events: none;
|
||||
`
|
||||
document.body.appendChild(toast)
|
||||
}
|
||||
toast.textContent = `缩放: ${Math.round(scale * 100)}%`
|
||||
// 初始化时根据showToast状态设置toastDuration的可见性
|
||||
toastDurationOption.hidden = !settings.options.showToast
|
||||
|
||||
// 3秒后移除提示
|
||||
clearTimeout((toast as any).timeoutId)
|
||||
;(toast as any).timeoutId = setTimeout(() => {
|
||||
toast.remove()
|
||||
}, 3000)
|
||||
} catch (error) {
|
||||
console.error('显示缩放提示失败', error)
|
||||
}
|
||||
// 监听showToast变化,动态控制toastDuration的可见性
|
||||
const onShowToastChange = (showToast: boolean) => {
|
||||
toastDurationOption.hidden = !showToast
|
||||
}
|
||||
|
||||
// 获取视频元素并应用缩放
|
||||
const applyScale = async () => {
|
||||
// 统一的应用缩放和显示提示逻辑
|
||||
const applyScaleAndShowToast = async (scale: number): Promise<void> => {
|
||||
try {
|
||||
// 使用playerAgent API获取视频元素
|
||||
const videoElement = await playerAgent.query.video.element()
|
||||
if (videoElement) {
|
||||
// 应用transform: scale()样式
|
||||
videoElement.style.transform = `scale(${currentScale})`
|
||||
videoElement.style.transformOrigin = 'center'
|
||||
await applyScale(scale)
|
||||
|
||||
// 检查是否启用了toast显示
|
||||
if (settings.options.showToast) {
|
||||
showScaleToast(scale, settings.options.toastDuration as number)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('视频缩放: 无法获取视频元素', error)
|
||||
handleError('应用缩放和显示提示', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据设置更新缩放
|
||||
const updateScaleFromSettings = async () => {
|
||||
const preset = settings.options.scalePreset as ScalePreset
|
||||
const handleUpdateScaleFromSettings = async (): Promise<void> => {
|
||||
try {
|
||||
const preset = settings.options.scalePreset as ScalePreset
|
||||
|
||||
if (preset === 'custom') {
|
||||
currentScale = settings.options.customScale as number
|
||||
} else {
|
||||
currentScale = SCALE_MAPPING[preset]
|
||||
// 当预设为自定义时显示滑动条,否则隐藏
|
||||
customScaleOption.hidden = preset !== '自定义'
|
||||
|
||||
const newScale = updateScaleFromSettings(
|
||||
preset,
|
||||
settings.options.customScale as number,
|
||||
scaleState,
|
||||
)
|
||||
|
||||
await applyScaleAndShowToast(newScale)
|
||||
} catch (error) {
|
||||
handleError('根据设置更新缩放', error)
|
||||
}
|
||||
|
||||
await applyScale()
|
||||
showScaleToast(currentScale)
|
||||
}
|
||||
|
||||
// 监听缩放预设变化
|
||||
const onScalePresetChange = async () => {
|
||||
await updateScaleFromSettings()
|
||||
}
|
||||
|
||||
// 监听自定义缩放变化
|
||||
const onCustomScaleChange = async (newValue: number) => {
|
||||
// 确保值在有效范围内
|
||||
currentScale = Math.max(minScale, Math.min(maxScale, newValue))
|
||||
await applyScale()
|
||||
showScaleToast(currentScale)
|
||||
const handleCustomScaleChange = async (newValue: number): Promise<void> => {
|
||||
try {
|
||||
const preset = settings.options.scalePreset as ScalePreset
|
||||
if (preset === '自定义') {
|
||||
// 确保值在有效范围内
|
||||
const newScale = updateScaleFromSettings(preset, newValue, scaleState)
|
||||
|
||||
await applyScaleAndShowToast(newScale)
|
||||
}
|
||||
} catch (error) {
|
||||
handleError('处理自定义缩放变化', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用addComponentListener监听设置变化
|
||||
addComponentListener('videoScaling.scalePreset', onScalePresetChange)
|
||||
addComponentListener('videoScaling.customScale', onCustomScaleChange)
|
||||
// 添加设置监听器
|
||||
addComponentListener('videoScaling.scalePreset', handleUpdateScaleFromSettings)
|
||||
addComponentListener('videoScaling.customScale', handleCustomScaleChange)
|
||||
addComponentListener('videoScaling.showToast', onShowToastChange)
|
||||
|
||||
// 初始化缩放
|
||||
updateScaleFromSettings().catch(err => {
|
||||
console.error('初始化视频缩放失败', err)
|
||||
})
|
||||
try {
|
||||
await handleUpdateScaleFromSettings()
|
||||
} catch (error) {
|
||||
handleError('初始化视频缩放', error)
|
||||
}
|
||||
|
||||
// 监听视频切换,重置缩放
|
||||
import('@/core/observer')
|
||||
.then(({ videoChange }) => {
|
||||
videoChange(() => {
|
||||
updateScaleFromSettings().catch(err => {
|
||||
console.error('重置视频缩放失败', err)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('导入observer失败', err)
|
||||
try {
|
||||
// 不存储返回值,直接调用videoChange
|
||||
videoChange(async () => {
|
||||
try {
|
||||
await handleUpdateScaleFromSettings()
|
||||
} catch (error) {
|
||||
handleError('重置视频缩放', error)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
handleError('导入observer', error)
|
||||
}
|
||||
|
||||
// 清理函数,在组件卸载时调用
|
||||
return () => {
|
||||
// 清理所有toast元素
|
||||
cleanupToasts()
|
||||
}
|
||||
},
|
||||
reload: () => {
|
||||
// 重新加载组件时执行清理
|
||||
document.querySelectorAll('.be-video-scale-toast').forEach(el => el.remove())
|
||||
cleanupToasts()
|
||||
},
|
||||
})
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { playerAgent } from '@/components/video/player-agent'
|
||||
import { SCALE_MAPPING, CUSTOM_SCALE_CONFIG } from './constants'
|
||||
import { ScalePreset } from './types'
|
||||
|
||||
// 缩放状态管理
|
||||
export class ScaleState {
|
||||
private currentScale = 1.0
|
||||
|
||||
get(): number {
|
||||
return this.currentScale
|
||||
}
|
||||
|
||||
set(value: number): void {
|
||||
this.currentScale = value
|
||||
}
|
||||
}
|
||||
|
||||
// 应用缩放效果
|
||||
export async function applyScale(scale: number): Promise<void> {
|
||||
try {
|
||||
// 使用playerAgent API获取视频元素
|
||||
const videoElement = await playerAgent.query.video.element()
|
||||
if (videoElement) {
|
||||
// 应用transform: scale()样式
|
||||
videoElement.style.transform = `scale(${scale})`
|
||||
videoElement.style.transformOrigin = 'center'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('视频缩放: 无法获取视频元素', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 从设置更新缩放值
|
||||
export function updateScaleFromSettings(
|
||||
preset: ScalePreset,
|
||||
customScale: number,
|
||||
scaleState: ScaleState,
|
||||
): number {
|
||||
// 确保值在有效范围内
|
||||
if (preset === '自定义') {
|
||||
const clampedValue = Math.max(
|
||||
CUSTOM_SCALE_CONFIG.min,
|
||||
Math.min(CUSTOM_SCALE_CONFIG.max, customScale),
|
||||
)
|
||||
scaleState.set(clampedValue / 100) // 转换为小数
|
||||
} else {
|
||||
scaleState.set(SCALE_MAPPING[preset])
|
||||
}
|
||||
|
||||
return scaleState.get()
|
||||
}
|
||||
@ -1,26 +1,9 @@
|
||||
// 视频缩放模式类型定义
|
||||
export type ScalingMode = 'none' | 'fill' | 'cover' | 'contain' | 'custom'
|
||||
// 定义缩放预设选项类型
|
||||
import { SCALE_PRESETS } from './constants'
|
||||
|
||||
// 缩放样式配置类型
|
||||
export type ScalingStyles = {
|
||||
[key in ScalingMode]: string
|
||||
}
|
||||
export type ScalePreset = (typeof SCALE_PRESETS)[number]
|
||||
|
||||
// 控制栏下拉菜单项接口
|
||||
export interface DropdownItem {
|
||||
id: ScalingMode
|
||||
text: string
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
// 缩放选项配置接口
|
||||
export interface ScalingOptions {
|
||||
scalingMode: ScalingMode
|
||||
customScale: number
|
||||
}
|
||||
|
||||
// 缩放状态接口
|
||||
export interface ScalingState {
|
||||
currentScalingMode: ScalingMode
|
||||
isCustomScaling: boolean
|
||||
// 为Toast元素添加超时ID类型
|
||||
export interface ToastWithTimeout extends HTMLDivElement {
|
||||
timeoutId?: number
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
import { TOAST_CLASS_NAME } from './constants'
|
||||
import { ToastWithTimeout } from './types'
|
||||
|
||||
// 统一的错误处理函数
|
||||
export function handleError(operation: string, error: unknown): void {
|
||||
console.error(`${operation}失败`, error)
|
||||
}
|
||||
|
||||
// 显示缩放比例提示
|
||||
export function showScaleToast(scale: number, duration: number): void {
|
||||
try {
|
||||
// 创建一个临时的toast元素显示缩放比例
|
||||
let toast = document.querySelector(`.${TOAST_CLASS_NAME}`) as ToastWithTimeout
|
||||
if (!toast) {
|
||||
toast = document.createElement('div')
|
||||
toast.className = TOAST_CLASS_NAME
|
||||
}
|
||||
|
||||
// 找到视频元素
|
||||
const videoElement = dq('video') || dq('bwp-video')
|
||||
if (videoElement) {
|
||||
// 将toast元素添加为视频元素的子元素或同级元素
|
||||
if (toast.parentNode !== document.body) {
|
||||
document.body.appendChild(toast)
|
||||
}
|
||||
|
||||
// 获取视频元素的位置信息
|
||||
const rect = videoElement.getBoundingClientRect()
|
||||
|
||||
// 设置toast元素样式
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: ${rect.top + rect.height / 2}px;
|
||||
left: ${rect.left + rect.width / 2}px;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
z-index: 9999;
|
||||
font-size: 16px;
|
||||
pointer-events: none;
|
||||
`
|
||||
|
||||
// 显示为百分比,使用Math.round确保整数显示
|
||||
toast.textContent = `缩放: ${Math.round(scale * 100)}%`
|
||||
|
||||
// 清除之前的超时定时器
|
||||
if (toast.timeoutId) {
|
||||
clearTimeout(toast.timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用用户设置的toast显示时间
|
||||
const timeoutMs = duration * 1000 // 转换为毫秒
|
||||
toast.timeoutId = setTimeout(() => {
|
||||
toast.remove()
|
||||
}, timeoutMs) as unknown as number
|
||||
} catch (error) {
|
||||
handleError('显示缩放提示', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理所有toast元素
|
||||
export function cleanupToasts(): void {
|
||||
document.querySelectorAll(`.${TOAST_CLASS_NAME}`).forEach(el => {
|
||||
// 清除可能存在的超时定时器
|
||||
const toast = el as ToastWithTimeout
|
||||
if (toast.timeoutId) {
|
||||
clearTimeout(toast.timeoutId)
|
||||
}
|
||||
el.remove()
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user