# simple-player-mix
**Repository Path**: jwshell/simple-player-mix
## Basic Information
- **Project Name**: simple-player-mix
- **Description**: 基于两个基础组件的混合播放器组件,本组件的编程思想、程序设计范式值得学习。可用于学习,禁止商用。
- **Primary Language**: JavaScript
- **License**: CC-BY-SA-4.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-05-30
- **Last Updated**: 2026-05-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: 播放器, player, Vue, vue2
## README
# 混合播放器(simple-player-mix)
组件代码最新地址:
## 功能清单
| 功能 | 支持 | 依赖 |
| :--: | :--: | :--: |
| 权限 | 是 | |
| 试看功能 | 是 | |
| 兼容H5 | 是 | |
| 浓缩回放 | 是 | simpleplayer大于2.3.14 |
| 异形屏 | 是 | |
| 本地配置 | 是 | simpleplayer大于2.4.14 |
## 如何引用 (默认极简)
1、安装依赖
```
"lodash": "^4.17.21",
"deepmerge": "^4.2.2",
"client-container": "^0.2.58",
"jsplugin": "^2.5.0",
```
还需支持`svg`标签:
`npm i svg-sprite-loader -D`
```
"svg-sprite-loader": "^6.0.11",
```
2、环境配置vue.config.js
```js
// 默认babel-loader会忽略node_modules中的文件
// 但是dolphin-plugin-tools用了es6的语法, 配置对其显示转译
// 配合babel sourceType: 'unambiguous'来使用 https://github.com/babel/babel/issues/9187,transpileDependencies: [
'dolphin-plugin-tools', /@hui-pro/, /hui/, /hi-map-vue/, /hi-map-core/, 'client-container']
```
关于`svg`相关的配置,增加如下loader:
```
chainWebpack: config => {
config.module
.rule('svg')
.exclude.add(path.resolve(__dirname, 'src/components/simple-player/assets/svg-icons'))
.end();
config.module
.rule('icons')
.test(/.svg$/)
.include.add(path.resolve(__dirname, 'src/components/simple-player/assets/svg-icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
});
},
```
注意: 此处的路径确认和你工程中的路径对应, 'src/components/simple-player/assets/svg-icons',如果有误需要按照你工程的路径配置。
3、vue中引用
```html
```
```js
import simplePlayer from '@/components/simple-player/index.vue';
// 获取当前选用哪一种播放器
function getPlayerType() {
try {
const KEY = 'BSQUARE_ARCH_TAGSRC_INFO';
const cache = sessionStorage.getItem(KEY);
const {playerType} = cache && JSON.parse(cache);
return playerType;
} catch (e) {
console.log('从缓冲中,获取播放器类型异常', e);
return 'client_container';
}
}
```
### 关于H5播放器如何使用(选用)
> 混合播放器中集成了H5播放器,使用前需要引入H5依赖的包
#### 前端需要做
1、安装依赖包`npm install jsplugin -S`
```
"jsplugin": "^2.5.0",
```
2、main.js中导入load函数
```js
// 这里写你播放器的路径,重点导入/h5/h5-load-script
import {
H5LoadScript,
setComponentPath
} from '@/components/simple-player/h5/h5-load-script';
// 此处需要指定你组件的componenentId
setComponentPath('ipoint-data', () => {
Vue.use(H5LoadScript);
});
```
3、要求jsPlugin必须放置于`/static/jsPlugin/`下
Vue工程中,将`jsPlugin`文件夹拷贝到放置于`src/public/static/`下。
**PS: 这里要重点关注下你项目打包问题,要保证打包后`jsPlugin`也是存在的。以免部署后出现路径问题。**
#### 后端需要做
1、需要提供两个接口,根据indexCode获取H5的取流信息的接口,一个是预览的一个是回放的。
#### 注意事项
1、生产环境中,H5播放器使用要求https必须是安全的,需要提前安装好证书。
## Vue中如何使用
### 播放器的属性以及方法
| 属性 | 类型 | 含义 |
| -- | -- | -- |
| player-type | String | 播放器类型,字典值是h5_player和client_container,默认client_container |
| api-methods | Object| api接口对象 |
| config | Object| 配置项对象 |
| receive | Function | 监听播放器所有事件回调函数 |
| notify-method | 事件 | 极简原生事件(选用) |
| onNotify | 事件 | 极简原生事件(选用) |
代码示例:
```html
```
script中导入(特别注意:如果你要使用`player-type`属性,则,导入一定是index.vue,如果你有个性需求,也可以只导入cc.vue或h5,vue,此时无须指定`player-type`属性)
```js
import simplePlayer from '@/components/simple-player/index.vue';
```
PS:关于api-methods的使用案例,请移步到最下方【事件注册DEMO】
### **api-methods**
| API名称 | 类型 | 含义 |
| -- | -- | -- |
| queryH5PreviewApi | Promise | H5预览查询接口 |
| queryH5PlaybackApi | Promise | H5回放查询接口 |
| queryAlgorithmsApi | Promise | 查询算法接口 |
| querySwitchStatusApi | Promise | 切换浓缩播放状态接口 |
| saveSwitchStatusApi | Promise | 保存浓缩播放切换接口 |
| queryPlaybackParamApi | Promise | 查询浓缩播放回放参数 |
| queryVideoByLabelApi | Promise | 查询录像片段数据 |
| queryPreviewAuthApi | Promise | 查询预览权限数据接口 |
| queryPlaybackAuthApi | Promise | 查询回放权限数据接口 |
| queryAlgorithmOptionApi | Promise | 查询单个点位的算法分析选项接口 |
详细解读:
- queryH5PreviewApi 和 queryH5PlaybackApi
要求返回规范的response,包括code等,例如:
```
{
code: 0,
data: {
url: '',
playbackUrl: ''
}
}
```
- queryAlgorithmOptionApi
要求的返回数据格式是Array,格式如下:
```
[
{
label: '选项1',
value: '1'
},
{
label: '选项2',
value: '2'
}
]
```
### **配置项 `config`**
| 属性 | 类型 | 含义 |
| -- | -- | -- |
| theme | String | 主题 |
| mode | Number| 播放器模式 |
| debug | String | 是否开启debug模式(开启后控制台有日志) |
| showMoreScreens | Boolean | 是否展示底部工具栏中异形屏按钮,默认false |
| showTitle | Boolean | 是否展示窗口标题,默认true |
| showToolBar | Boolean | 是否展示全局底部工具栏,默认true |
| showHeader | Boolean | 是否展示全局头部工具栏,默认true |
| showSetting | Boolean | 是否展示极简播放器器右上侧的配置按钮,默认true |
| showSequence | Boolean | 是否展示窗口编号,默认false |
| showAlgorithmAnalysis | Boolean | 是否展示算法分析功能,默认false |
| showLabelTag | Boolean | 是否展示标签标记功能,默认false |
| showEventReport | Boolean | 是否展示事件上报功能,默认false |
| showPictureTag | Boolean | 是否展示图片标签功能,默认false |
| showAiRecommend | Boolean | 是否展示智能推荐功能,默认false |
| openPtzWidthMini | Boolean | 当点击打开云台时,是否默认打开mini云台控制窗口,默认false |
| strictAuthMode | Boolean | 权限严格模式,false则不校验能力集,默认不校验能力集 |
| hasApplyAuth | Boolean | 是否可以申请权限,默认true |
| maxScreen | Number | 允许的播放器使用的最大分屏数,默认9 |
| videoTryTime | Number | 试看次数,默认3 |
| checkTool | Boolean | 是否检测插件助手,默认true |
| capabilitySetKey | String | 能力集字段名称,默认capabilitySet |
| globalTopHeader | Object | 全局头部,{ display: true, 是否展示 hasConcentrate: true, 是否有浓缩播放 } |
| globalToolBar | Object | 全局底部工具栏,{ display: true, 是否展示 hasPlayerBtnGroup: true, 是否有播放器按钮组 } |
| readStreamWay | Number | 极简取流方式。默认1。0-通过URL取流;1-通过indexCode取流;2-自动识别 |
| queryH5PreviewParamKeys | Array | 指定H5预览参数,默认['indexCode', 'streamType', 'protocol', 'transmode', 'expand'] |
| queryH5PlaybackParamKeys| Array | 指定H5回放参数,默认['indexCode', 'startTime', 'endTime', 'recordType', 'recordStyle', 'protocol', 'dataType', 'lockType', 'expand'] |
示例:
- 展示标题中的序号
```
:config="{ mode: 3, showTitle: true, showSequence: false }"
```
- 展示算法分析功能
```
:config="{ mode: 3, maxScreen: 9, showAlgorithmAnalysis: true }"
```
### **事件**
`receive`函数接收播放器内部事件,参数有四个参数
| 参数 | 类型 | 含义 |
| -- | -- | -- |
| type | String | 事件类型 |
| data | Object | 返回数据对象 |
| callback | Function | 回调函数 |
| event_type | String | 事件类型字典值 |
`data`格式如下:
```
{
selectWnd, // 当前选中窗格
screen, // 当前分屏数
list, // 当前可视中加载的数据
currentPage, // 当前页中的数据
pages, // 分页信息
// 以下是其他个性参数,因事件类型决定,部分事件data的数据会有下面参数(打印参数联调查看)
index, // 窗口下标
status, // 窗口状态
wnd, // 窗口对象
item // 当前窗口数据
}
```
PS: 其他个性参数,因事件类型决定(打印参数联调查看)
`EVENT_TYPE`事件类型字典值如下:
| 事件名称 | 触发点 | 含义 |
| -- | -- | -- |
| SHOW_PLAYER_INFO | 点击窗口头部状态栏中的信息图标 | 播放器头部按钮信息点击触发 |
| TRY_SEE_OVER | 试看被动检测触发启动后 | 单次试看结束 |
| GLOBAL_PLAYER_STATUS | 全局预览回放状态切换时 | 全局预览回放状态切换 |
| STOP_PLAY | 被动停止播放时 | 停止播放 |
| START_PLAY | 开始播放时 | 开始播放触发 |
| APPLY_AUTH | “申请权限”相关点击时 | 申请权限触发 |
| APPLY_AUTH_OPENED | 申请权限图层被打开时 | 申请权限层打开 |
| REFRESH | 取流失败后,点击“刷新重试”按钮触发 | 刷新 |
| FIRST_PAGE | 被动初始化首页时 | 初始化首页触发 |
| PRE_PAGE | 点击底部状态栏时 | 前一页触发 |
| GO_PAGE | 跳转到某一页时 | 跳转页触发 |
| NEXT_PAGE | 向后一页时 | 后一页触发 |
| SELECT_WND | 选中视频窗格时 | 选中窗口触发 |
| CHANGE_SCREEN | 改变分屏数时 | 改变分屏触发 |
| CLOSE_WND | 关闭窗格时 | 关闭窗口触发 |
| SINGLE_PREVIEW | 单个窗格预览后被动触发 | 单个播放器预览 |
| SINGLE_PLAYBACK | 单个窗格回放后被动触发 | 单个播放器回放 |
| BATCH_PREVIEW | 批量预览被动触发 | 批量播放器预览 |
| BATCH_PLAYBACK | 批量回放被动触发 | 批量播放器回放 |
| PLAYBACK_FAIL | 某个点位回放失败被动触发 | 回放失败 |
| PREVIEW_FAIL | 某个点位预览失败被动触发 | 预览失败 |
| PLAYBACK_SUCCESS | 某个点位回放成功被动触发 | 回放成功 |
| PREVIEW_SUCCESS | 某个点位预览成功被动触发 | 预览成功 |
| CLOSE_ALL_WND | 关闭所有窗格时 | 关闭所有窗口 |
| CLOSE_ALL_BTN_CLICK | 点击关闭所有窗格时 | 关闭所有按钮点击事件 |
| WND_STATUS_CHANGE | 窗口状态变化时 | 窗口状态变化 |
| BATCH_ALGORITHM_ANALYSIS | 播放器顶部“算法分析”点击后 | 批量算法分析 |
| SINGLE_ALGORITHM_ANALYSIS | 某个窗口算法分析点击后 | 单个算法分析 |
| LABEL_TAG| 某个标记标记点击后 | 标签标记 |
| AI_RECOMMEND | 点击智能推荐时 | 智能推荐 |
| TIME_INTERVAL_CHANGE | 改变轮播时间时 | 轮播时间被改变事件 |
| MOVE_RIGHT | | 向右滑动 |
| MOVE_LEFT | | 向左滑动 |
| BEFORE_GO_TO_PAGE | 切换翻页 | 翻页之前
| SINGLE_LOOP_END | 完成一次轮播时 | 单次轮询结束
| PTZ_TOP | mini云台控制窗口点击事件 | 云台控制向上
| PTZ_LEFT | mini云台控制窗口点击事件 | 云台控制向左
| PTZ_RIGHT | mini云台控制窗口点击事件 | 云台控制向右
| PTZ_BOTTOM | mini云台控制窗口点击事件 | 云台控制向下
| PTZ_LEFT_TOP | mini云台控制窗口点击事件 | 云台控制向左上
| PTZ_LEFT_BOTTOM | mini云台控制窗口点击事件 | 云台控制向左下
| PTZ_RIGHT_TOP | mini云台控制窗口点击事件 | 云台控制向右上
| PTZ_RIGHT_BOTTOM | mini云台控制窗口点击事件 | 云台控制向右下
| PTZ_FOCUS | mini云台控制窗口点击事件 | 云台控制聚焦
| PTZ_OUT_OF_FOCUS | mini云台控制窗口点击事件 | 云台控制失焦
### **开放的API**
> 一个前提条件:看到这里,接下来所有的api说明中,都假设有一个前提,就是你已经拿到了点位数据,在调用播放器的api的时候,需要你对你拿到的数据进行map处理,处理成播放器要求的数据格式。
> 一个注意点:如果你的应用涉及到了权限,那么在调用以前任何一种方法时,均要考虑是否需要调用`doAddAuth`,一般情况下,是需要的。
#### **doAddAuth**
更新权限
| 参数 | 类型 | 是否必填 | 含义 |
| -- | -- | -- | -- |
| pointInfos | Array | 是 | 视频点位对象集合 |
#### **doReplacePlay**
替换到当前选中(无选中则指定_index替换)窗口
| 参数 | 类型 | 是否必填 | 含义 |
| -- | -- | -- | -- |
| pointInfo | Object | 是 | 视频点位对象 |
| _index | Number| 否 | 窗口下标(不传则默认替换当前选中窗口) |
| playStatus | String | 否 | 预览回放标识 |
**补充说明:**
- 替换原则:
1)不允许在同一屏下添加重复点(如果添加重复点则锚点到该点处)
2)替换到某个窗口前,一定会先从缓冲中读取,无则新增缓冲数据
3)无缓冲存在情况下,添加点位会丢失历史的权限信息
#### **doPlayback**
批量回放,不传参则默认刷新
| 参数 | 类型 | 是否必填 | 含义 |
| -- | -- | -- | -- |
| pointInfo | String | 是 | 视频点位对象 |
关于参数pointInfo中核心参数说明:
| 字段名称 | 类型 | 是否必填 | 含义 |
| -- | -- | -- | -- |
| indexCode | String | 是 | 点位ID |
| url | String | 否 | 取流URL(对于极简播放器生效,rtsp协议取流地址) |
| recordStyle | Number | 否 | 存储类型 |
| transmode | Number | 否 | 取流方式 |
| streamType | Number | 否 | 码流类型 |
| startTime | String | 否 | 回放开始时间(+8区国际时间格式)|
| endTime | String | 否 | 回放结束时间(+8区国际时间格式)|
| _expandParams | Object | 否 | 扩展字段 |
关于字段补充说明(重要)
- 回放时间段startTime和endTime,格式是:国际标准时间+8区时间,YYYY-MM-DDTHH:mm:ss.SSSZ,如果不传递则默认倒数一天起止时间。
- 非必传并不是说可以不传,要按照真实的接口返回为准,有则一定要传,没有则不传,否则将影响回放效果。
- 默认取流方式是indexCode,可以指定URL方式取流,也可以自适应方式取流。通过指定配置项`readStreamWay`实现。
关于H5的回放参数`h5Params`说明:
| 字段名称 | 类型 | 是否必填 | 含义 |
| -- | -- | -- | -- |
| streamType | String | 是 | 码流类型(按照接口返回如实传递) |
| transmode | Number | 否 | 取流方式(按照接口返回如实传递) |
| protocol | Number | 否 | 协议类型 |
| startTime | Number | 否 | 回放开始时间(+8区国际时间格式) |
| endTime | String | 否 | 回放结束时间(+8区国际时间格式)|
例如:
```
{
indexCode,
transmode,
streamType,
// ...,
h5Params: {
streamType: 0,
transmode: 1,
protocol: getH5Protocol(),
startTime: moment(range[0]).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
endTime: moment(range[1]).format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
}
}
```
PS: 关于`getH5Protocol`方法,可以引用播放器中的。
```js
import {getH5Protocol} from "@/components/simple-player/util";
```
---
---
---
---
---
---
## 扩展阅读
**设计原则**
原则一:播放器抽象成类`SimplePlayerData`
原则二:播放器侧的功能解耦,独立到`lib`中,极简播放器是`cc.lib.js`,H5播放器是`h5.lib.js`
原则三:事件、配置项、权限、开放的API等都解耦到`mixin`中
### 重构原因
- 播放器变量分散,难以维护
- 相当一部分方法中存在较多的冗余逻辑
- 国产化和基线测试中修改缺陷频频,难收敛
- 播放器template中代码越来越多,越来越难以阅读
- 现有的窗格提示信息难以增加扩展,为了解决自定义窗口各种图层的问题。
- 能解决Pro不能不解决的权限问题。
- 能解决Pro没有的H5的问题。
- 重构后易于二次开发扩展。
### 详细解读 功能点
#### 目录结构
```
├─simple-player
| |
│ └─asssets
| | | svg-icons(播放器中图标库,靠近设计,仅支持svg,随放随用)
│ └─components
| | | AlgorithmModal.vue(浓缩播放中算法选择弹框)
| | | Concentrate.vue(浓缩播放插件)
| | | CoverDialog.vue(全局弹框)
| | | DateDialog.vue(日期选择器)
| | | DefaultCameraImg.vue(默认图片)
| | | GlobalLayer.vue(全局图层入口文件)
| | | LoopPlayTimeSetting.vue(轮询组件)
| | | PlayerFooter.vue(窗格尾部组件)
| | | PlayerHeader.vue(窗格头部组件)
| | | PlayerLayer.vue(窗格遮罩层组件)
| | | SpeedSelect.vue(倍数播放组件)
| | | SvgI.vue(svg图标组件)
| | | TimeInterval.vue(轮询时间间隔选择组件)
| | | ToolBar.vue(全局底部工具栏)
| | | VideoPlanPlayHeader.vue(全局头组件)
│ └─lib
| | | cc.lib.js(client-container库)
| | | checker.lib.js(预留)
| | | h5.lib.js(h5库)
| | | index.js(外放库,供给外部调用)
| | | notify.lib.js(提示封装库)
| | | params.lib.js(核心数据)
| | | player.lib.js(核心类)
| | | text.lib.js (文本封装)
│ └─layer(遮罩图层库)
│ └─mixin
| | | ApiProps.mixin.js(API插件)
| | | Concentrate.mixin.js
| | | Config.mixin.js(配置项插件)
| | | Event.mixin.js(事件插件)
| | | LoopPlay.mixin.js
| | | OpenApiCommon.mixin.js(开放API插件)
| | | OpenApiForCc.mixin.js(开放API插件-极简专用)
| | | OpenApiForH5.mixin.js(开放API插件-h5专用)
| | | PlayerStyle.mixin.js
| | | SpFooter.mixin.js
| | | SpHeader.mixin.js
│ └─index.vue
```
#### 播放器的数据结构
```
{
isDelete: false, // 非必需,点位是否已被删除
indexCode: "", // 点位编码
title: "", // 非必需,点位名称
sequence: "", // 非必需,序号(启用showTitle后)
sequenceHtml: "", // 非必需,序号Html(启用showTitle后)
recordType: '', // 非必需, 录像类型 传空查询全部录像片段 0|1|2|6 0 定时录像 1 移动侦测 2 报警触发 6 手动录像
transmode: 1, // 非必需, 0 UDP 1 TCP streamType: 0, // 非必需,0 主码流 1 子码流
status: "preview", // 非必需,内部使用
capabilitySet: '', // 非必需,能力集 (默认非严格模式,严格模式下会使用该字段,配置中strictAuthMode为true)
cascadeType: 0, // 非必需,点位级联属性(废弃,该字段无用)
cameraType: 0, // 点位类型(重要字段,涉及到操作工具栏显隐)
h5Params: {
url: '', // 取流URL预览
playbackUrl: '', // 取流URL回放
szURL: null, // 取流URL
playURL: null, proxy: null, mode: 0, // 必传;0 普通模式 1 高级模式; 默认0
beginTime: null, // 回放必传开始时间,实时预览不需要传入
endTime: null, // 回放必传结束时间,实时预览不需要传入
previewFail: null, // 预览失败编码,H5内部不支持错误码展示,所以组件要单独显示
playbackFail: null, // 回放失败描述,H5内部不支持错误码展示,所以组件要单独显示
},
extraParams: {
action: {
playSnapAuth: false,
playbackSnapAuth: false,
digitalZoomAuth: false,
ptzAuth: false,
downloadAuth: false,
audioRecvAuth: false,
previewRecordAuth: false,
playbackRecordAuth: false,
tdZoomAuth: false,
talkAuth: false,
},
wndStatus: { // 非必需(TRY_SEE_OVER事件钩子中维护,可不传)
previewAuth: true, // 预览权限
remainPreviewCount: 3, // 预览剩余次数
playbackAuth: true, // 回放权限
remainPlaybackCount: 3, // 回放剩余次数
}
}
}
```
> 功能点介绍详见线上文档
#### 事件注册DEMO
`api-methods`中提前注册声明API对象,demo如下:
```js
apiMethodsMap: {
queryH5PreviewApi: (params) => getH5PreviewUrl({
previewUrlParamList: [params]
}).then((res) => {
// 返回包括code的完整的返回体。下面这样处理的原因是,真正的返回体包裹在indexCode中,所以要读取indexCode。
return res && res.data && res.data[params.indexCode] && res.data[params.indexCode];
}),
queryH5PlaybackApi: (params) => getPlayBackUrl({playBackUrlParamList: [params]}).then((res) => {
return res && res.data && res.data[params.indexCode] && res.data[params.indexCode];
}),
queryAlgorithmsApi: getAlgorithmsApi,
querySwitchStatusApi: getSwitchStatusApi,
saveSwitchStatusApi: saveSwitchStatus,
queryPlaybackParamApi: getPlaybackParamApi,
queryVideoByLabelApi: queryVideoByLabelApi,
queryPreviewAuthApi: (params) => {
if (this.openAuthMode) {
return Promise.all([
resourcesByParamsApi({
cameraIndexCodes: [params.indexCode],
authCodes: ['preview']
}),
getRenmainderApi({
indexCode: params.indexCode
})
]).then(([r1, r2]) => {
return {
previewAuth: r1.data && r1.data.includes(params.indexCode), // 预览权限
remainPreviewCount: r2.data && r2.data.operateTimes && r2.data.operateTimes[0] && r2.data.operateTimes[0].playRealNum, // 预览剩余次数
};
});
} else {
return new Promise((resolve) => {
resolve({
previewAuth: true,
remainPreviewCount: 3
});
});
}
},
queryPlaybackAuthApi: (params) => {
if (this.openAuthMode) {
return Promise.all([
resourcesByParamsApi({
cameraIndexCodes: [params.indexCode],
authCodes: ['playback']
}),
getRenmainderApi({
indexCode: params.indexCode
})
]).then(([r1, r2]) => {
return {
playbackAuth: r1.data && r1.data.includes(params.indexCode), // 回放权限
remainPlaybackCount: r2.data && r2.data.operateTimes && r2.data.operateTimes[0] && r2.data.operateTimes[0].playBackNum, // 回放剩余次数
};
});
} else {
return new Promise((resolve) => {
resolve({
playbackAuth: true,
remainPlaybackCount: 3
});
});
}
},
}
```
## 文档修订记录
| 修订内容 | 修订人 | 修订时间 |
| -- | -- | -- |
| 初始化文档 | liyaolin(changed) | 2024-04-12 |
| 增加新功能说明,点搜使用的算法申请`showAlgorithmAnalysis` | liyaolin(changed) | 2024-04-16 |
| 增加了安装依赖部分说明,主要是svg部分,变更了实现方案,以后必须安装`svg-sprite-loader`并配置 | liyaolin(changed) | 2024-06-26 |
| 补充部分章节新增的功能说明 | liyaolin(changed) | 2025-03-18 |
## 版权申明
播放器组件 遵从CC BY-NC-SA 4.0开源协议。明确授权查看、修改,但严令禁止商业用途。