veepai-video-player-pro
v1.1.0
Published
企业级 HLS 视频播放器组件,支持回放片段时间轴、精灵图预览、多片段自动切换等功能
Maintainers
Readme
@veepai/video-player-pro
企业级 HLS 视频播放器组件库,基于 Vue3 + TypeScript + xgplayer 开发,专为视频监控回放系统设计。
✨ 核心特性
- 🎬 HLS 视频播放 - 支持跨浏览器兼容,自动选择最佳播放策略(Safari 原生 HLS / Chrome HLS 插件)
- ⏱️ 回放片段时间轴 - 基于 Canvas 绘制的高性能时间轴,支持缩放、拖动、蓝色区域标记
- 🖼️ 精灵图预览 - 鼠标悬停时间轴显示视频预览帧,支持边界检测
- 🔄 多片段自动切换 - 自动播放连续的回放片段
- ⏸️ 智能暂停控制 - 拖动到非回放区域自动暂停,点击播放回到上次有效位置
- 📱 移动端支持 - 完善的触摸事件支持(拖动、双指缩放)
- 🎨 可定制化 - 时间轴颜色、播放器配置均可自定义
📦 安装
npm install @veepai/video-player-pro
# 或
yarn add @veepai/video-player-pro
# 或
pnpm add @veepai/video-player-pro依赖说明
组件的核心依赖和播放器库已内置,安装 NPM 包后会自动安装以下依赖:
内置依赖(自动安装):
[email protected]和[email protected](通过别名机制同时支持 v2/v3)[email protected]和[email protected]
项目中需要安装(peerDependencies):
# 必须安装的核心依赖
npm install vue@^3.0.0 dayjs@^1.11.0注意:xgplayer 相关依赖已内置在组件库中,无需在项目中重复安装。
🚀 快速开始
全局注册
// main.ts
import { createApp } from 'vue'
import VideoPlayerPro from '@veepai/video-player-pro'
import App from './App.vue'
const app = createApp(App)
app.use(VideoPlayerPro)
app.mount('#app')局部注册
<template>
<div class="replay-container">
<VideoPlayer
ref="playerRef"
device-id="DEVICE001"
user-id="USER001"
:date-picker-tiem="timeRange"
:player-height="500"
:timeline-height="60"
:timeline-colors="customColors"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { VideoPlayer } from 'veepai-video-player-pro'
import type { ReplayItem, SpriteData, TimelineColors } from 'veepai-video-player-pro'
// 导入样式
import 'veepai-video-player-pro/dist/style.css'
const playerRef = ref()
const timeRange = ref(['2024-01-01 10:00:00', '2024-01-01 11:00:00'])
// 自定义时间轴颜色
const customColors: TimelineColors = {
background: '#1E1E1E',
meddleLine: '#FF0000',
scaleText: '#00FF00',
}
// ✅ API 适配器实现(参考 视频云存储监控中心的接口调用,请注意后端接口需要同步修改,保证响应的数据格式一致,
// 后端:欧阳jw
// )
const fetchReplayData = async (params: any): Promise<ReplayItem[]> => {
const res = await getReplayData(params)
return res as any // requestDevice 已在响应拦截器中返回 response.data
}
const fetchSprite = async (params: any): Promise<SpriteData> => {
const res = await getSprite(params)
return res as any
}
// 手动刷新播放列表
const refreshPlaylist = () => {
playerRef.value?.deviceBackPlayerList(timeRange.value)
}
</script>📖 API 文档
VideoPlayer 组件
Props
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| ----------------- | ------------------ | ---- | ------------ | --------------------------------------- |
| deviceId | string | ✅ | - | 设备 ID |
| userId | string | ✅ | - | 用户 ID |
| datePickerTiem | string[] | ❌ | 最近1小时 | 时间范围选择 ['开始时间', '结束时间'] |
| m3u8BaseUrl | string | ❌ | http://... | m3u8 接口基础 URL |
| playerHeight | string \| number | ❌ | '400px' | 播放器高度(支持 '500px' 或 500) |
| timelineHeight | number | ❌ | 44 | 时间轴高度(px) |
| timelineColors | TimelineColors | ❌ | 见下方 | 时间轴颜色自定义配置 |
| onGetReplayData | Function | ⚠️ | - | 自定义回放数据获取方法(必须提供) |
| onGetSprite | Function | ⚠️ | - | 自定义精灵图获取方法(必须提供) |
| mock | boolean | ❌ | false | 是否启用模拟数据 |
onGetReplayData 参数说明
interface GetReplayDataParams {
deviceid: string
userid: string
begin: number // 开始时间戳(毫秒)
end: number // 结束时间戳(毫秒)
channel: number
provider: string
bucket: string
}
interface ReplayItem {
duration: number // 持续时间(秒)
startTime: number // 开始时间戳(毫秒)
endTime: number // 结束时间戳(毫秒)
key: string // m3u8 文件名
}
// 返回值:Promise<ReplayItem[]>onGetSprite 参数说明
interface GetSpriteParams {
deviceid: string
userid: string
begin: number // 开始时间戳(毫秒)
end: number // 结束时间戳(毫秒)
channel: number
}
interface SpriteData {
data: string // base64 图片数据
meta: {
webp_width: number // 雪碧图总宽度
webp_height: number // 雪碧图总高度
sprite_width: number // 单个精灵图宽度
sprite_height: number // 单个精灵图高度
total: number // 精灵图总数量
}
}
// 返回值:Promise<SpriteData>Methods
| 方法名 | 参数 | 返回值 | 说明 |
| ---------------------- | ---------------- | --------------- | ------------ |
| deviceBackPlayerList | date: string[] | Promise<void> | 刷新回放列表 |
使用示例
<script setup lang="ts">
import { ref } from 'vue'
import { VideoPlayer } from '@veepai/video-player-pro'
const playerRef = ref()
// 刷新播放列表
const refresh = () => {
playerRef.value?.deviceBackPlayerList(['2024-01-01 12:00:00', '2024-01-01 13:00:00'])
}
</script>
<template>
<VideoPlayer
ref="playerRef"
device-id="DEVICE001"
user-id="USER001"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
<button @click="refresh">刷新</button>
</template>TimeLine 组件
可单独使用时间轴组件(不包含播放器)。
Props
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| -------------- | ------------------ | ---- | -------- | --------------------------- |
| width | string \| number | ❌ | '100%' | 时间轴宽度 |
| height | number | ❌ | 44 | 时间轴高度(px) |
| setStartTime | string | ❌ | - | 初始显示时间 |
| playTime | string | ❌ | - | 当前播放时间 |
| timeRange | string[] | ❌ | [] | 时间范围 ['开始', '结束'] |
| markTime | MarkTimeItem[] | ❌ | [] | 回放片段标记 |
| isAutoPlay | boolean | ❌ | false | 是否自动播放 |
| colors | TimelineColors | ❌ | 见下方 | 自定义颜色配置 |
| minPxSecond | number | ❌ | 65 | 最小像素秒 |
MarkTimeItem 类型
interface MarkTimeItem {
beginTime: string // 'YYYY-MM-DD HH:mm:ss'
endTime: string // 'YYYY-MM-DD HH:mm:ss'
bgColor: string // '#2AC3E4'
}TimelineColors 默认值
{
background: '#2D2D2D', // 深灰色背景
meddleLine: '#FFFFFF', // 中心线白色
meddleDate: '#FFFFFF', // 中间时间白色
moveLine: '#FFFFFF', // 移动线白色
moveDate: '#FFFFFF', // 移动时间白色
scaleLine: '#5A5A5A', // 刻度线灰色
scaleBar: '#1E1E1E', // 刻度背景更深的灰色
scaleText: '#CCCCCC', // 刻度文字浅灰色
smallScaleLine: '#404040' // 小刻度线颜色
}Events
| 事件名 | 参数 | 说明 |
| --------------- | -------------- | ---------------- |
| slideToMark | date: number | 拖动到某个时间点 |
| change | time: string | 时间变化 |
| requestSprite | data: {...} | 请求精灵图 |
| clearSprite | - | 清空精灵图 |
Methods
| 方法名 | 参数 | 说明 |
| ---------------- | ---------------------------- | ---------- |
| zoomIn | - | 放大时间轴 |
| zoomOut | - | 缩小时间轴 |
| draw | - | 重新绘制 |
| setSpriteImage | {url, x, y, width, height} | 设置精灵图 |
单独使用示例
<template>
<TimeLine
ref="timelineRef"
:mark-time="markTimeList"
:play-time="currentPlayTime"
:time-range="['2024-01-01 00:00:00', '2024-01-01 23:59:59']"
:colors="customColors"
@slide-to-mark="handleSlideToMark"
@request-sprite="handleRequestSprite"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { TimeLine } from '@veepai/video-player-pro'
import type { MarkTimeItem } from '@veepai/video-player-pro'
const timelineRef = ref()
const currentPlayTime = ref('2024-01-01 10:30:00')
const markTimeList = ref<MarkTimeItem[]>([
{
beginTime: '2024-01-01 10:00:00',
endTime: '2024-01-01 10:30:00',
bgColor: '#2AC3E4',
},
{
beginTime: '2024-01-01 11:00:00',
endTime: '2024-01-01 11:15:00',
bgColor: '#2AC3E4',
},
])
const customColors = {
background: '#1E1E1E',
meddleLine: '#FF0000',
scaleText: '#00FF00',
}
const handleSlideToMark = (timestamp: number) => {
console.log('滑动到:', new Date(timestamp))
}
const handleRequestSprite = (data: any) => {
console.log('请求精灵图:', data)
}
</script>🎯 高级用法
1. 自定义播放器和时间轴高度
<script setup lang="ts">
import { VideoPlayer } from '@veepai/video-player-pro'
// 可以使用字符串或数字
const playerHeight = 500 // 数字会自动转为 px
// const playerHeight = '500px' // 也可以使用字符串
// const playerHeight = '50vh' // 支持其他 CSS 单位
</script>
<template>
<VideoPlayer
device-id="DEVICE001"
user-id="USER001"
:player-height="playerHeight"
:timeline-height="60"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
</template>2. 自定义时间轴颜色
<script setup lang="ts">
import { VideoPlayer } from '@veepai/video-player-pro'
import type { TimelineColors } from '@veepai/video-player-pro'
const customColors: TimelineColors = {
background: '#1a1a1a', // 背景色
meddleLine: '#ff0000', // 中心线颜色
meddleDate: '#ffffff', // 中间时间颜色
scaleLine: '#666666', // 刻度线颜色
scaleText: '#00ff00', // 刻度文字颜色
}
</script>
<template>
<VideoPlayer
device-id="DEVICE001"
user-id="USER001"
:timeline-colors="customColors"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
</template>3. 自定义 M3U8 URL 生成
<script setup lang="ts">
import { VideoPlayer } from '@veepai/video-player-pro'
const customM3u8BaseUrl = 'https://your-cdn.com/m3u8'
// 组件会自动拼接参数
</script>
<template>
<VideoPlayer
device-id="DEVICE001"
user-id="USER001"
:m3u8-base-url="customM3u8BaseUrl"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
</template>4. 禁用埋点日志
<template>
<VideoPlayer
device-id="DEVICE001"
user-id="USER001"
:enable-logger="false"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
</template>5. 动态切换时间范围
<script setup lang="ts">
import { ref } from 'vue'
import { VideoPlayer } from '@veepai/video-player-pro'
const playerRef = ref()
const timeRange = ref(['2024-01-01 10:00:00', '2024-01-01 11:00:00'])
const changeTimeRange = () => {
const newRange = ['2024-01-01 14:00:00', '2024-01-01 15:00:00']
playerRef.value?.deviceBackPlayerList(newRange)
}
</script>
<template>
<div>
<VideoPlayer
ref="playerRef"
device-id="DEVICE001"
user-id="USER001"
:date-picker-tiem="timeRange"
:on-get-replay-data="getReplayDataAPI"
:on-get-sprite="getSpriteAPI"
/>
<button @click="changeTimeRange">切换时间段</button>
</div>
</template>6. 完整的 API 集成示例
// api.ts
import type { ReplayItem, SpriteData } from '@veepai/video-player-pro'
export async function getReplayData(params: {
deviceid: string
userid: string
begin: number
end: number
channel: number
provider: string
bucket: string
}): Promise<ReplayItem[]> {
const response = await fetch('https://api.example.com/replay/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
return data.items || []
}
export async function getSprite(params: {
deviceid: string
userid: string
begin: number
end: number
channel: number
}): Promise<SpriteData> {
const response = await fetch('https://api.example.com/replay/sprite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}<!-- App.vue -->
<script setup lang="ts">
import { VideoPlayer } from '@veepai/video-player-pro'
import { getReplayData, getSprite } from './api'
const timeRange = ['2024-01-01 10:00:00', '2024-01-01 11:00:00']
</script>
<template>
<VideoPlayer
device-id="DEVICE001"
user-id="USER001"
:date-picker-tiem="timeRange"
:on-get-replay-data="getReplayData"
:on-get-sprite="getSprite"
m3u8-base-url="https://your-cdn.com/m3u8"
logger-env="prod"
/>
</template>🔧 浏览器兼容性
| 浏览器 | 版本 | 播放策略 | 说明 | | -------------- | ---- | -------- | -------- | | Chrome | 90+ | HLS 插件 | 推荐使用 | | Safari | 14+ | 原生 HLS | 最佳体验 | | Firefox | 88+ | HLS 插件 | 支持 | | Edge | 90+ | HLS 插件 | 支持 | | iOS Safari | 14+ | 原生 HLS | 完美支持 | | Android Chrome | 90+ | HLS 插件 | 支持 |
注意事项:
- macOS Chrome 在 ARM64 架构下可能存在解码问题,建议使用 Safari
- 组件会自动检测环境并选择最佳播放策略
📝 核心功能说明
1. 时间轴拖动逻辑
- 拖动到蓝色区域(回放片段):自动跳转并播放
- 拖动到非蓝色区域:暂停播放,指针停留在拖动位置
- 点击播放按钮:自动回到上次有效播放位置(蓝色区域)
2. 精灵图预览
- 鼠标悬停在蓝色区域时自动请求并显示视频预览帧
- 支持边界检测,防止精灵图超出容器被裁剪
- 离开区域自动清空缓存
3. 多片段播放
- 自动识别连续的回放片段
- 片段结束自动切换到下一片段
- 支持同一 m3u8 文件的多个片段连续播放
4. 埋点日志
记录以下事件:
pageIn/pageOut: 页面进入/离开click: 用户点击操作network: 网络请求error: 错误信息live: 播放器状态
⚠️ 注意事项
必须提供 API 方法:组件不包含内置的数据获取逻辑,必须通过
onGetReplayData和onGetSpriteprops 提供自定义 API 方法xgplayer 版本:支持 v2.x 和 v3.x,组件会根据环境自动选择版本
时间格式:所有时间参数统一使用
'YYYY-MM-DD HH:mm:ss'格式样式隔离:组件使用
scoped样式,不会影响全局样式性能优化:
- 精灵图请求有1秒节流限制
- Canvas 绘制优化,支持高 DPI 屏幕
- 自动释放 Blob URL 防止内存泄漏
🤝 贡献
欢迎提交 Issue 和 Pull Request!
📄 许可证
MIT License
