vue-easy-player
v1.1.1
Published
Vue 2 视频播放器组件,支持 H265/HEVC 编码的 HLS(m3u8)、HTTP-FLV、WS-FLV,集成 WASM 软解码,即装即用
Downloads
767
Maintainers
Readme
vue-easy-player
Vue 2 视频播放器组件,基于 EasyDarwin EasyPlayer.js 官方 SDK 封装,完整映射官方 API。
即装即用,支持 H265/HEVC 硬解 + 软解码,支持 HTTP-FLV、WS-FLV、HLS(m3u8)、WebRTC、fMP4 等多种协议,支持直播、点播(Seek/倍速/多清晰度)。
目录
- 特性
- 环境要求
- 安装
- 快速开始
- Props 完整列表
- Events 完整列表
- 实例方法(通过 $refs 调用)
- 自定义控制栏(多实例)
- H265/HEVC 播放原理
- 内部加载机制
- 命名说明
- 视频流格式自动识别
- 常见问题
- 开发与发布
特性
- 即装即用:零静态文件配置,所有脚本/WASM 全部内联到 bundle,消费者无需拷贝任何静态资源
- 零额外依赖:安装后无需再安装 crypto-js 等依赖,SDK 及其运行时全部打包内联
- 协议全覆盖:HLS(m3u8) / HTTP-FLV / WS-FLV / WebRTC / fMP4 / 裸流
- H265 软解码:通过 WASM 实现浏览器端 H265/HEVC 解码
- 多解码模式:MSE 硬解码 / WebCodec / WASM(含 SIMD) / WebGPU
- 多实例隔离:每个播放器实例拥有独立的 Worker 和 WASM 运行时,互不影响,可同时播放多路视频
- 自定义控制栏:通过
showControls=false+btns精细控制,父组件自定义 UI - 完整事件:映射官方 SDK 所有 20+ 个事件
- 完整方法:通过
$refs暴露所有 15+ 个播放器实例方法
环境要求
| 项目 | 要求 | |------|------| | Vue | 2.6+ | | 浏览器 | Chrome 51+ / Firefox 54+ / Edge 79+ / Safari 10+ | | WASM | 播放 H265 时需要浏览器支持 WebAssembly(主流浏览器均支持) |
安装
npm install vue-easy-player安装完成后无需任何额外操作——无需拷贝静态文件、无需配置 publicPath、无需安装额外依赖。
快速开始
方式一:插件注册(推荐)
// main.js
import Vue from "vue";
import VueEasyPlayer from "vue-easy-player";
Vue.use(VueEasyPlayer);<!-- App.vue -->
<template>
<EasyPlayer :playerId="1" videoUrl="https://example.com/live/stream.m3u8" />
</template>方式二:按需引入
<script>
import { EasyPlayer } from "vue-easy-player";
export default {
components: { EasyPlayer },
};
</script>方式三:HTML UMD 标签引入
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-easy-player/dist/vue-easy-player.umd.js"></script>
<!-- 自动注册:<EasyPlayer> 即可使用 -->Props 完整列表
基础配置
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| playerId ★ | Number\|String | 必填 | 播放器唯一 ID,多实例必须区分 |
| videoUrl ★ | String | 必填 | 视频流地址 |
| isLive | Boolean | true | true=直播,false=点播(支持 Seek/倍速) |
| idIndex | Number | undefined | 已废弃,使用 playerId 替代,保留以兼容旧代码 |
★ 为必填项
控制栏
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| showControls | Boolean\|String | true | true=显示所有 / false=完全隐藏 / "partial"=按 btns 精细控制 |
| btns | Object | 见下方 | 按钮精细控制对象 |
// btns 默认值(对应 SDK 官方 operateBtns 配置)
{
fullscreen: true, // 全屏按钮
screenshot: true, // 截图按钮
play: true, // 播放/暂停按钮
audio: true, // 音量/静音按钮
record: true, // 录制按钮
stretch: true, // 拉伸切换按钮
zoom: false, // 电子放大按钮
ptz: false, // 云台控制按钮
quality: false, // 清晰度切换按钮
close: false, // 关闭按钮
scale: false, // 缩放按钮
logSave: false, // 日志保存按钮
}音频
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| hasAudio | Boolean | true | hasAudio |
| isMute | Boolean | false | isMute |
显示
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| stretch | Boolean | true | stretch |
| poster | String | "" | poster |
| controlAutoHide | Boolean | false | controlAutoHide — 鼠标不动时自动隐藏控制栏,移入时显示 |
| isLogo | Boolean | true | isLogo — 是否显示播放器 LOGO |
| loadingIcon | Boolean | true | loadingIcon — 是否显示加载动画图标 |
| loadingText | String | "" | loadingText — 加载中提示文字 |
缓冲 & 重连
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| bufferTime | Number | 1 | 最小缓冲时长(秒) |
| loadTimeOut | Number | 10 | 加载超时(秒) |
| loadTimeReplay | Number | 3 | 重连次数,-1 无限 |
解码模式
| Prop | 类型 | 默认值 | SDK 映射 | 说明 |
|------|------|--------|----------|------|
| MSE | Boolean | false | MSE | MSE 硬解码(H264 优先) |
| WCS | Boolean | false | WCS | WCS 硬解码 |
| WASM | Boolean | false | WASM | WASM 软解码 |
| WASMSIMD | Boolean | false | WASMSIMD | WASM-SIMD 加速 |
| gpuDecoder | Boolean | false | gpuDecoder | WebGPU 硬解码 |
| isFlv | Boolean | false | isFlv | 强制 FLV 解码 |
| webGPU | Boolean | false | webGPU | WebGPU 渲染 |
| canvasRender | Boolean | false | canvasRender | Canvas 渲染 |
协议特殊配置
| Prop | 类型 | 默认值 | SDK 映射 | 说明 |
|------|------|--------|----------|------|
| isRtcSRS | Boolean | false | isRtcSRS | SRS 类型 WebRTC |
| isRtcZLM | Boolean | false | isRtcZLM | ZLM 类型 WebRTC |
| isFlow | Boolean | false | isFlow | 裸流模式 |
水印
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| watermark | Object | null | { text: {content,color,opacity,fontSize}, right, top } |
| fullWatermark | Object | null | { text, angle, color, fontSize, opacity } |
<!-- 局部水印示例 -->
<EasyPlayer
:playerId="1"
videoUrl="..."
:watermark="{ text: { content: '监控-01', color: 'rgba(255,255,255,0.7)', opacity: 0.6, fontSize: '14px' }, right: 10, top: 10 }"
/>清晰度
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| quality | Array | null | ['普清','高清','超清','4K','8K'] |
| defaultQuality | String | "" | 默认清晰度名 |
云台控制
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| ptzConfig | Object | null | { ptz: true, ptzMore: true } |
调试
| Prop | 类型 | 默认值 | SDK 映射 |
|------|------|--------|----------|
| debug | Boolean | false | debug — 开启控制台日志 |
| showBandwidth | Boolean | true | showBandwidth — 显示网速状态 |
| isBand | Boolean | undefined | @deprecated,使用 showBandwidth 替代 |
高级自定义
| Prop | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| options | Object | {} | 直接传递给 SDK 构造函数的配置,优先级最高 |
<!-- 当 props 无法覆盖的配置,通过 options 直传 -->
<EasyPlayer
:playerId="1"
videoUrl="..."
:options="{ MSE: true, gpuDecoder: true, debug: true }"
/>Events 完整列表
所有事件的回调参数中均包含 playerId,方便多实例场景区分来源。
播放生命周期
| 事件 | 参数 | 说明 |
|------|------|------|
| player-created | { playerId, player, err } | 播放器实例创建完成(err 非 null 表示初始化失败) |
| play | playerId | 播放开始 |
| pause | playerId | 暂停 |
| liveEnd | playerId | 直播结束 |
| playbackSeek | { playerId, time } | 点播跳转完成 |
| playbackRate | { playerId, rate } | 倍速变化 |
| timestamps | { playerId, timestamp } | 播放时间戳回调 |
错误与超时
| 事件 | 参数 | 说明 |
|------|------|------|
| error | { playerId, error } | 播放异常 |
| timeout | playerId | 加载超时 |
媒体信息
| 事件 | 参数 | 说明 |
|------|------|------|
| videoInfo | { playerId, info } | 视频参数 |
| audioInfo | { playerId, info } | 音频参数 |
| decodeHevc | { playerId, info } | 编码类型回调 |
交互状态
| 事件 | 参数 | 说明 |
|------|------|------|
| fullscreen | { playerId, isFullscreen } | 全屏状态变化 |
| mute | { playerId, isMute } | 静音状态变化 |
| stretch | { playerId, isStretch } | 拉伸切换 |
| kBps | { playerId, speed } | 当前网速 kB/s(每秒触发) |
| qualityChange | { playerId, qualityName } | 清晰度切换完成 |
| ptz | { playerId, data } | 云台操作数据 |
| screenshots | { playerId, data } | 截图完成回调 |
| contextmenuClose | playerId | 右键菜单关闭 |
录制
| 事件 | 参数 | 说明 |
|------|------|------|
| recordStart | playerId | 录制开始 |
| recordEnd | { playerId, fileInfo } | 录制结束 |
事件使用示例
<template>
<EasyPlayer
:playerId="1"
videoUrl="..."
:showControls="false"
@player-created="onCreated"
@play="onPlay"
@error="onError"
@kBps="onSpeed"
@fullscreen="onFullscreen"
@screenshots="onScreenshot"
/>
</template>
<script>
export default {
methods: {
onCreated({ playerId, player, err }) {
if (!err) console.log(`播放器 ${playerId} 就绪`);
},
onPlay(playerId) {
console.log(`通道 ${playerId} 开始播放`);
},
onError({ playerId, error }) {
console.error(`通道 ${playerId} 异常:`, error);
},
onSpeed({ playerId, speed }) {
console.log(`通道 ${playerId} 网速: ${speed} kB/s`);
},
onFullscreen({ playerId, isFullscreen }) {
console.log(`通道 ${playerId} 全屏: ${isFullscreen}`);
},
onScreenshot({ playerId, data }) {
console.log(`通道 ${playerId} 截图完成`, data);
},
},
};
</script>实例方法(通过 $refs 调用)
完整映射 EasyPlayer.js 官方实例方法。
播放控制
| 方法 | 参数 | 说明 |
|------|------|------|
| play(url?) | url 直播流地址(可选) | 播放直播流 |
| playback(url?) | url 视频地址(可选) | 点播播放 |
| pause() | - | 暂停 |
| getIsPause() | - | 返回是否暂停 Boolean |
| setRate(rate) | rate: Number | 设置回放倍速 |
| seekTime(seconds) | seconds: Number | 跳转到指定时间(点播) |
音频
| 方法 | 参数 | 说明 |
|------|------|------|
| setMute(state) | state: Boolean | 静音/取消静音 |
| getIsMute() | - | 返回是否静音 Boolean |
| setMic(state) | state: Boolean | 对讲开关(需 PTZ 开启) |
全屏
| 方法 | 参数 | 说明 |
|------|------|------|
| setFullscreen() | - | 进入全屏 |
| exitFullscreen() | - | 退出全屏 |
截图
| 方法 | 参数 | 说明 |
|------|------|------|
| screenshot(filename?, format?, quality?, type?) | filename: 文件名 / format: 'png'|'jpeg'|'webp' / quality: 0-1 / type: 'download'|'base64'|'blob' | 截图,返回 base64/Blob |
清晰度
| 方法 | 参数 | 说明 |
|------|------|------|
| setQuality(name) | name: String | 切换清晰度 |
信息获取
| 方法 | 参数 | 说明 |
|------|------|------|
| getVideoInfo() | - | 获取视频信息 |
| getAudioInfo() | - | 获取音频信息 |
生命周期
| 方法 | 参数 | 说明 |
|------|------|------|
| destroy() | - | 销毁播放器(组件卸载时自动调用) |
| reload() | - | 销毁旧实例 -> 重新初始化(切换源后重置) |
自定义控制栏(多实例)
这是最常用的高级场景:关闭 SDK 自带控制栏,父组件自己实现控制 UI,同时管理多个播放器实例。
多实例隔离保证:每个播放器实例拥有独立的 Web Worker 和 WASM 运行时,实例之间互不影响——切换、销毁任一实例不会导致其他实例画面中断。
<template>
<div class="monitor-page">
<!-- 4 路视频同时播放 -->
<div class="grid">
<div v-for="ch in channels" :key="ch.id" class="cell">
<EasyPlayer
:ref="'player_' + ch.id"
:playerId="ch.id"
:videoUrl="ch.url"
:showControls="false"
:isLive="true"
:stretch="true"
@player-created="onPlayerCreated"
@error="onError"
@kBps="onSpeed"
/>
<!-- 自定义控制栏 -->
<div class="custom-controls">
<span class="channel-label">通道 {{ ch.id }}</span>
<button @click="playChannel(ch.id)">▶</button>
<button @click="pauseChannel(ch.id)">⏸</button>
<button @click="fullscreenChannel(ch.id)">⛶</button>
<button @click="snapshotChannel(ch.id)">📷</button>
<button @click="muteChannel(ch.id)">
{{ mutes[ch.id] ? '🔇' : '🔊' }}
</button>
<span class="speed">{{ speeds[ch.id] }} kB/s</span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
channels: [
{ id: 1, url: "https://example.com/live/ch1.m3u8" },
{ id: 2, url: "https://example.com/live/ch2.m3u8" },
{ id: 3, url: "https://example.com/live/ch3.m3u8" },
{ id: 4, url: "https://example.com/live/ch4.m3u8" },
],
players: {},
mutes: {},
speeds: {},
};
},
methods: {
getComponent(playerId) {
return this.$refs["player_" + playerId];
},
onPlayerCreated({ playerId, player, err }) {
if (err) {
console.error(`通道 ${playerId} 初始化失败`, err);
return;
}
this.players[playerId] = player;
this.$set(this.mutes, playerId, false);
},
playChannel(playerId) {
this.getComponent(playerId)?.play();
},
pauseChannel(playerId) {
this.getComponent(playerId)?.pause();
},
fullscreenChannel(playerId) {
this.getComponent(playerId)?.setFullscreen();
},
snapshotChannel(playerId) {
const base64 = this.getComponent(playerId)?.screenshot("snapshot", "png", 0.8, "base64");
if (base64) {
console.log(`通道 ${playerId} 截图:`, base64.substring(0, 50) + "...");
}
},
muteChannel(playerId) {
const comp = this.getComponent(playerId);
if (comp) {
const current = comp.getIsMute();
comp.setMute(!current);
this.$set(this.mutes, playerId, !current);
}
},
onError({ playerId, error }) {
console.error(`通道 ${playerId} 错误:`, error);
},
onSpeed({ playerId, speed }) {
this.$set(this.speeds, playerId, speed);
},
},
};
</script>注意:由于 Vue 2 组件中 method 不能与 prop 同名,查询方法使用
get前缀:getIsMute()、getIsPause()。
H265/HEVC 播放原理
为什么需要 WASM 软解码
主流浏览器 HLS 实现(无论是原生 <video> 标签还是 HLS.js)只支持 H264 解码,不支持 H265/HEVC。H265 在监控摄像头、安防行业广泛使用。
本插件解决方案
videoUrl = "https://.../stream.m3u8"
│
▼
EasyPlayer SDK 加载
│
▼
HLS.js 解析播放列表 & 检测 TS 分片的 codec
│
┌───┴───┐
▼ ▼
H264 H265 (HEVC)
│ │
▼ ▼
浏览器原生 WASM 软解码器
解码播放 (EasyPlayer-pro.wasm)
│
▼
Canvas 渲染画面整个过程对开发者透明,只需传入流地址即可。也可以手动指定解码模式:
<!-- 强制 WASM -->
<EasyPlayer :playerId="1" videoUrl="..." :WASM="true" />
<!-- WASM-SIMD 加速 -->
<EasyPlayer :playerId="1" videoUrl="..." :WASMSIMD="true" />内部加载机制
本插件采用全内联策略,将 SDK 的所有运行时文件打包进 bundle,消费者无需关心静态文件路径:
EasyPlayer-pro.js → asset/source 内联为字符串 → Blob URL 动态 <script> 注入
EasyPlayer-lib.js → asset/source 内联为字符串 → 每次 new Worker() 时创建独立 Blob URL
EasyPlayer-decode.js → asset/source 内联为字符串 → 每次 new Worker() 时创建独立 Blob URL
EasyPlayer-pro.wasm → asset/inline 编码为 base64 → 拦截 fetch/XHR,每次返回 ArrayBuffer 副本
EasyPlayer-snap.wasm → asset/inline 编码为 base64 → 拦截 fetch/XHR,每次返回 ArrayBuffer 副本多实例隔离实现
| 隔离维度 | 实现方式 |
|----------|----------|
| Worker 隔离 | 拦截 window.Worker 构造函数,每次创建播放器时为 EasyPlayer-lib.js / EasyPlayer-decode.js 生成新的 Blob URL,确保每个实例拥有独立的解码 Worker |
| WASM 隔离 | 拦截 fetch / XMLHttpRequest,每次 .wasm 请求返回原始 ArrayBuffer 的 .slice(0) 副本,避免 WebAssembly.instantiate 消费后 buffer detached |
| SDK 实例隔离 | 每个组件通过 new EasyPlayerPro(container, config) 创建独立实例,channelId 由 playerId prop 决定,避免 SDK 内部状态冲突 |
命名说明
import VueEasyPlayer from "vue-easy-player" // 插件对象(有 install 方法)
Vue.use(VueEasyPlayer) // 内部: Vue.component("EasyPlayer", 组件)
<EasyPlayer /> // 标签名 = 组件的 name 属性也可以绕过插件直接拿组件:
import { EasyPlayer } from "vue-easy-player"; // 不需要 Vue.use()视频流格式自动识别
| URL 特征 | 识别为 | 内核 |
|----------|--------|------|
| http(s)://.../xxx.m3u8 | HLS | HLS.js |
| http(s)://.../xxx.flv | HTTP-FLV | FLV.js |
| ws(s)://.../xxx.flv | WS-FLV | FLV.js (WebSocket) |
| http(s)://.../xxx.mp4 | MP4 点播 | 原生 MSE |
| webrtc://... | WebRTC | SDK 内置 |
H265/HEVC 编码由 HLS.js 解析 TS 分片后自动检测,SDK 在编码不支持时自动切换到 WASM 解码器。
常见问题
Q1: url /xxx must be "http:" or "https:" or "ws:" protocol
原因:传入的 videoUrl 是相对路径,SDK 要求完整 URL。
解决:使用完整地址 https://your-domain.com/path/stream.m3u8。
Q2: EasyPlayer-lib.js:1 Uncaught SyntaxError
原因:SDK Web Worker 脚本路径无法解析。
解决:本插件 >= 1.1.0 已内联 Worker 脚本,升级即可。
Q3: 如何实现多画面同时播放?
见 自定义控制栏(多实例) 完整示例。每个实例拥有独立的 Worker 和 WASM 运行时,互不影响。
Q4: 多实例时已播放的窗口画面中断/不再加载
原因:旧版本中所有实例共享同一个 Worker Blob URL 和同一个 ArrayBuffer(WASM 二进制),第一个实例消费后 buffer 被 transfer/detach,后续实例无法再编译 WASM。
解决:本插件 >= 1.1.0 已修复——每次创建 Worker 时生成新的 Blob URL,每次 WASM 请求返回 ArrayBuffer.slice(0) 副本。升级即可。
Q5: 如何切换视频源?
this.currentUrl = "https://new-source.com/stream.m3u8"; // 响应式更新
// 或调用方法
this.$refs.player_1.play("https://new-source.com/stream.m3u8");Q6: 如何获取截图并上传?
const base64 = this.$refs.player_1.screenshot("capture", "jpeg", 0.8, "base64");
fetch("/api/upload", { method: "POST", body: JSON.stringify({ image: base64 }) });Q7: WebRTC 如何配置?
<!-- SRS WebRTC -->
<EasyPlayer :playerId="1" videoUrl="webrtc://..." :isRtcSRS="true" />
<!-- ZLM WebRTC -->
<EasyPlayer :playerId="1" videoUrl="webrtc://..." :isRtcZLM="true" />Q8: 安装后项目中有哪些文件?
node_modules/vue-easy-player/
├── package.json
├── README.md
└── dist/
├── vue-easy-player.umd.js (~11 MB)
├── vue-easy-player.umd.min.js
├── vue-easy-player.common.js
└── *.map零额外文件 -- 所有 SDK/WASM 已内联。无需 postinstall 拷贝、无需配置 publicPath。
Q9: 打包发布到生产环境后播放器加载失败?
原因:旧版本中 SDK 脚本经过 babel 转译后内联了开发机器上的绝对路径 require("D:/..."),在生产环境中无法解析。
解决:本插件 >= 1.1.0 已修复——SDK 文件通过 asset/source 规则以原始文本内联,不经过 babel 转译。升级即可。
开发与发布
# 本地开发
npm run serve
# 构建库(输出到 dist/)
npm run lib
# 发布
npm publishLicense
MIT
