feat: 添加视频缩放功能及样式支持

This commit is contained in:
weedy233 2025-09-02 09:55:38 +08:00
parent ba2cd3bc03
commit dc9edeaee7
No known key found for this signature in database
GPG Key ID: 27A2575D0916FDD7
3 changed files with 256 additions and 0 deletions

View File

@ -0,0 +1,193 @@
import {
defineComponentMetadata,
defineOptionsMetadata,
OptionsOfMetadata,
} from '@/components/define'
import { componentsTags } from '@/components/types'
import { addStyle } from '@/core/style'
import { addMenuItem } from '@/components/video/video-context-menu'
import { addControlBarButton } from '@/components/video/video-control-bar'
import { ScalingMode, DropdownItem } from './types'
// 导入样式
import './styles.scss'
const options = defineOptionsMetadata({
scalingMode: {
defaultValue: 'none' as ScalingMode,
displayName: '缩放模式',
dropdownEnum: [
{ label: '默认', value: 'none' },
{ label: '拉伸填充', value: 'fill' },
{ label: '保持比例填充', value: 'cover' },
{ label: '保持比例显示', value: 'contain' },
{ label: '自定义缩放', value: 'custom' },
],
},
customScale: {
defaultValue: 1.2,
displayName: '自定义缩放比例',
hidden: false,
slider: {
min: 1,
max: 2,
step: 0.05,
},
},
})
export type Options = OptionsOfMetadata<typeof options>
// 全局变量存储当前缩放模式
let currentScalingMode: ScalingMode = 'none'
let cleanupMenuItems: HTMLElement[] = []
export const component = defineComponentMetadata({
name: 'videoScaling',
displayName: '视频内容缩放',
description: '调整视频显示比例,去除黑边,提供多种缩放模式',
tags: [componentsTags.video],
author: { name: 'weedy233', link: 'https://github.com/weedy233' },
entry: async () => {
// 获取当前设置
currentScalingMode = component.options.scalingMode.defaultValue
// 获取缩放样式
function getScalingStyles(mode: ScalingMode): string {
switch (mode) {
case 'fill':
return `
.bilibili-player-video video,
video[class^="bpx-player-video-element"],
.bilibili-player-video .bpx-player-video-element {
object-fit: fill !important;
}
`
case 'cover':
return `
.bilibili-player-video video,
video[class^="bpx-player-video-element"],
.bilibili-player-video .bpx-player-video-element {
object-fit: cover !important;
}
`
case 'contain':
return `
.bilibili-player-video video,
video[class^="bpx-player-video-element"],
.bilibili-player-video .bpx-player-video-element {
object-fit: contain !important;
}
`
case 'custom':
return `
.bilibili-player-video video,
video[class^="bpx-player-video-element"],
.bilibili-player-video .bpx-player-video-element {
object-fit: fill !important;
transform: scale(${component.options.customScale.defaultValue}) !important;
transform-origin: center !important;
}
`
default:
return ''
}
}
// 应用缩放样式
function applyScaling(mode: ScalingMode) {
// 移除之前的样式
document.head.querySelector('[data-id="video-scaling"]')?.remove()
if (mode === 'none') {
return
}
const styles = getScalingStyles(mode)
addStyle(styles, 'video-scaling')
}
// 切换缩放模式
function switchScalingMode(mode: ScalingMode) {
currentScalingMode = mode
applyScaling(mode)
}
// 下拉菜单项配置
const dropdownItems: DropdownItem[] = [
{ id: 'none', text: '默认', onClick: () => switchScalingMode('none') },
{ id: 'fill', text: '拉伸填充', onClick: () => switchScalingMode('fill') },
{ id: 'cover', text: '保持比例填充', onClick: () => switchScalingMode('cover') },
{ id: 'contain', text: '保持比例显示', onClick: () => switchScalingMode('contain') },
{ id: 'custom', text: '自定义缩放', onClick: () => switchScalingMode('custom') },
]
// 注册控制栏按钮
await addControlBarButton({
name: 'video-scaling',
displayName: '缩放',
icon: '\u{ea42}', // 缩放图标
order: 900,
action: () => {
// 在实际实现中,这里会打开下拉菜单
console.log('缩放按钮被点击')
},
})
// 添加右键菜单
const scalingMenuDiv = document.createElement('div')
scalingMenuDiv.textContent = '视频缩放'
cleanupMenuItems = []
await addMenuItem(scalingMenuDiv, menu => {
// 清理之前添加的菜单项
cleanupMenuItems.forEach(item => item.remove())
cleanupMenuItems.length = 0
if (!menu.isOpen) {
return
}
// 添加子菜单项
dropdownItems.forEach((item, index) => {
const subMenuItem = document.createElement('li')
subMenuItem.classList.add('context-line', 'context-menu-function')
subMenuItem.setAttribute('data-append', '1')
subMenuItem.style.paddingLeft = '40px'
const linkWrapper = document.createElement('a')
linkWrapper.classList.add('context-menu-a', 'js-action')
linkWrapper.href = 'javascript:void(0);'
linkWrapper.textContent = item.text
linkWrapper.addEventListener('click', e => {
e.preventDefault()
e.stopPropagation()
item.onClick()
// 关闭菜单
menu.containerElement.classList.remove('active')
})
subMenuItem.appendChild(linkWrapper)
menu.listElement.insertBefore(
subMenuItem,
menu.listElement.children[menu.itemElements.length + index],
)
cleanupMenuItems.push(subMenuItem)
})
})
// 准备清理函数
const cleanup = () => {
// 移除样式
document.head.querySelector('[data-id="video-scaling"]')?.remove()
scalingMenuDiv.remove()
cleanupMenuItems.forEach(item => item.remove())
}
// 初始应用缩放
applyScaling(currentScalingMode)
return cleanup
},
options,
})

View File

@ -0,0 +1,37 @@
/* 视频缩放组件样式补充 */
/* 确保缩放功能在不同播放器版本中都能正常工作 */
.bilibili-player-video {
position: relative;
overflow: hidden;
}
.bpx-player-video-container {
position: relative;
overflow: hidden;
}
/* 自定义缩放时的平滑过渡效果 */
.bilibili-player-video video,
video[class^="bpx-player-video-element"],
.bilibili-player-video .bpx-player-video-element {
transition: transform 0.3s ease;
}
/* 控制栏按钮样式优化 */
.bilibili-player-video-control-bottom-right {
display: flex;
align-items: center;
}
/* 右键菜单样式补充 */
.bilibili-player-context-menu-item {
cursor: pointer;
transition: background-color 0.2s ease;
}
/* 确保在全屏模式下缩放功能正常 */
.bilibili-player-video:-webkit-full-screen video,
.bilibili-player-video:fullscreen video {
position: relative;
}

View File

@ -0,0 +1,26 @@
// 视频缩放模式类型定义
export type ScalingMode = 'none' | 'fill' | 'cover' | 'contain' | 'custom'
// 缩放样式配置类型
export type ScalingStyles = {
[key in ScalingMode]: string
}
// 控制栏下拉菜单项接口
export interface DropdownItem {
id: ScalingMode
text: string
onClick: () => void
}
// 缩放选项配置接口
export interface ScalingOptions {
scalingMode: ScalingMode
customScale: number
}
// 缩放状态接口
export interface ScalingState {
currentScalingMode: ScalingMode
isCustomScaling: boolean
}