refactor: 重构视频缩放组件并添加新功能

- 重构代码结构,将功能拆分为多个模块
- 实现自定义缩放范围的配置
- 改进提示显示功能,增加持续时间设置
- 添加错误处理机制和清理函数
This commit is contained in:
weedy233 2025-09-02 15:26:53 +08:00
parent c7b3e79b53
commit 894eb2823e
No known key found for this signature in database
GPG Key ID: 27A2575D0916FDD7
5 changed files with 280 additions and 125 deletions

View File

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

View File

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

View File

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

View File

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

View File

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