Jessibuca 播放器
1. 什么是 Jessibuca
Jessibuca 是一个基于 web 端的 HTML5 视频播放器库,专门用来播放实时或点播的 FLV/RTMP/WebRTC 视频流,无需额外插件。
它的核心特点是:
- 纯前端解码:通过 JavaScript + WebAssembly 实现视频解码。
- 跨浏览器支持:Chrome、Firefox、Edge 等现代浏览器均可使用。
- 高性能:支持硬件加速和多路视频播放。
- 开源:可以根据需求修改或封装功能。
2. 支持的视频流类型
Jessibuca 支持的流类型主要有:
流类型 | 描述 |
FLV(Flash Video) | 通过 HTTP 或 WebSocket 获取 FLV 流 |
RTMP(Real-Time Messaging Protocol) | 主要用于直播推流(通常后台服务器转成 FLV) |
HLS(HTTP Live Streaming) | 基于分段的直播或点播,Jessibuca 也能支持部分 HLS |
WebRTC | 实时低延迟视频通道,适合监控或会议 |
在前端大多用的是 WebSocket FLV 流,因为无需 Flash,延迟低,兼容性好。
3. 工作原理(核心流程)
- 获取视频流:
后端推送 FLV 数据到 WebSocket 或 HTTP。
前端 Jessibuca 通过new Jessibuca({url, el})
接收。 - 解析视频数据
FLV 包含 H.264 视频帧和 AAC 音频帧。
Jessibuca 将二进制流解析成帧数据。 - 渲染视频:
使用 HTML5<canvas>
或<video>
元素渲染。
支持多路并发播放,通过独立实例管理。 - 控制操作:
暂停/播放:player.pause()
/player.play()
静音/声音:player.mute()
/player.unmute()
全屏:player.requestFullscreen()
截图:player.capturePicture()
返回 base64 图像
4. 使用场景与优势
Jessibuca 最常用的场景主要有:
场景 | 说明 |
视频监控 | 安防摄像头实时视频展示,多路同时播放 |
远程会议 | WebRTC 或低延迟直播流显示 |
在线教育 | 老师推流,学生端实时观看 |
直播平台 | FLV/WebRTC 直播流播放 |
视频回放 | 将点播视频流转换为可播放的 Canvas/Video |
优势:
- 前端解码:无需服务器转码到 MP4 或 HLS,减少延迟。
- 支持多路并发:可同时播放多路视频而互不干扰。
- 灵活封装:可以和 Vue、React、Angular 等框架结合。
- 接口简单:提供播放、暂停、截图、静音、全屏等 API。
5. 在 vue 中如何使用 Jessibuca
Jessibuca 官网 也有很多 demo
5.1. 下载文件
首先去官网下载 jessibuca.js、 decoder.js、decoder.wasm
放到 public 下
5.2. index.html 引入
然后在 index.html 引入
<script type="text/javascript" src="/js/jessibuca/jessibuca.js"></script>
5.3. 封装 vue 文件
用 Vue 3 封装了一个 Jessibuca 播放器组件。使用时只需传入 播放地址 和 是否显示控件 两个参数,就能快速集成视频播放功能。
此外,我还封装了 播放、暂停、截图、静音、全屏 等自定义操作,让视频交互更灵活,适合多路视频播放场景。
<template><div ref="container" style="width:100%; height: 100%; background-color: #000000;margin:0 auto;position: relative;"@dblclick="fullscreenSwich"><div style="width:100%; padding-top: 56.25%; position: relative;" /></div><div id="buttonsBox" class="buttons-box" v-if="showButton"><div class="buttons-box-left"><el-icon v-if="!playing" @click="playBtnClick" title="播放"><VideoPlay /></el-icon><el-icon v-if="playing" @click="pause" title="暂停"><VideoPause /></el-icon><el-icon @click="destroy" title="停止"><SwitchButton /></el-icon><el-icon v-if="isNotMute" @click="mute" title="静音"><MuteNotification /></el-icon><el-icon v-if="!isNotMute" @click="cancelMute" title="取消静音"><Bell /></el-icon></div><div class="buttons-box-right"><span class="jessibuca-btn">{{ kBps }} kb/s</span><el-icon class="jessibuca-btn" style="font-size: 1rem !important" @click="screenshot" title="截图"><Camera /></el-icon><el-icon class="jessibuca-btn" @click="playBtnClick" title="刷新"><Refresh /></el-icon><el-icon v-if="!fullscreen" class="jessibuca-btn" @click="fullscreenSwich" title="全屏"><FullScreen /></el-icon><el-icon v-if="fullscreen" class="jessibuca-btn" @click="fullscreenSwich" title="退出全屏"><Rank /></el-icon></div></div>
</template><script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { useRoute } from 'vue-router';
// import { ElMessage } from 'element-plus';
import { Camera, Bell, MuteNotification, VideoPlay, VideoPause, SwitchButton, Refresh, FullScreen, Rank } from '@element-plus/icons-vue';const props = defineProps({videoUrl: String,showButton: { type: Boolean, default: true },hasAudio: { type: Boolean, default: true },showButton: { type: Boolean, default: true },
});
const emit = defineEmits(['playStatusChange', 'playTimeChange']);const container = ref(null);// ---- Vue 3 ref 管理播放器实例 ----
const jessibucaPlayer = ref(null);const playing = ref(false);
const isNotMute = ref(true);
const quieting = ref(false);
const fullscreen = ref(false);
const loaded = ref(false); // loaded 代表是否加载完成
const performance = ref('');
const kBps = ref(0);
const playerTime = ref(0);
const currentVideoUrl = ref('');const route = useRoute();// --- 创建 Jessibuca 实例 ---
const create = () => {if (!window.Jessibuca) {console.error('[Jessibuca] window.Jessibuca 未找到,请检查 script 是否加载。');return;}const options = {container: container.value,videoBuffer: 0,isResize: true,useMSE: true,useWCS: false,text: '',hotKey: true,debug: false, // 是否开启调试模式,开启后会有更多的日志输出hasAudio: props.hasAudio,isNotMute: isNotMute.value,forceNoOffscreen: true,loadingTimeout: 10,keepScreenOn: true,loadingTimeoutReplay: true,loadingTimeoutReplayTimes: 3,operateBtns: {fullscreen: false,screenshot: false,play: false,audio: false,recorder: false,},loadingText: '请稍等, 视频加载中......',};try {jessibucaPlayer.value = new window.Jessibuca(options);const jessibuca = jessibucaPlayer.value;// 监听播放事件jessibuca.on('play', () => {playing.value = true;loaded.value = true;});// 监听暂停事件jessibuca.on('pause', () => {console.log('pause');playing.value = false;loaded.value = false;});jessibuca.on("kBps", (value) => {kBps.value = Math.round(value)})// 监听停止事件jessibuca.on('stop', () => {playing.value = false;loaded.value = false});// 监听播放结束事件jessibuca.on('end', () => { })jessibuca.on('fullscreen', (msg) => {fullscreen.value = msg;});jessibuca.on('mute', (msg) => {isNotMute.value = !msg;});jessibuca.on('performance', (val) => {if (val === 2) performance.value = '非常流畅';else if (val === 1) performance.value = '流畅';else performance.value = '卡顿';});} catch (err) {console.error('[Jessibuca] new Jessibuca 报错:', err);jessibucaPlayer.value = null;}
};const createPlayer = () => {return new Promise((resolve) => {create();// 这里监听 play 或者 nextTick 确保播放器初始化完成nextTick(() => resolve());});
};// --- 播放 ---
const playBtnClick = () => {play(currentVideoUrl.value);
};const play = async (url) => {currentVideoUrl.value = url;// 等待销毁完成await destroyPlayer();// 等待创建完成await createPlayer();// 播放视频if (jessibucaPlayer.value) {jessibucaPlayer.value.play(currentVideoUrl.value);}
};// --- 暂停 ---
const pause = () => {if (jessibucaPlayer.value) jessibucaPlayer.value.pause();
};// --- 销毁 ---
const destroy = () => {if (jessibucaPlayer.value) jessibucaPlayer.value.destroy();jessibucaPlayer.value = null;playing.value = false;loaded.value = false;performance.value = '';playerTime.value = 0;
};const destroyPlayer = () => {return new Promise((resolve) => {if (jessibucaPlayer.value) {destroy();// nextTick 确保 DOM 更新完成再 resolvenextTick(() => resolve());} else {resolve();}});
};// --- 截图 ---
const screenshot = () => {if (jessibucaPlayer.value) jessibucaPlayer.value.screenshot();
};// --- 静音 / 取消静音 ---
const mute = () => {if (jessibucaPlayer.value) jessibucaPlayer.value.mute();
};const cancelMute = () => {if (jessibucaPlayer.value) jessibucaPlayer.value.cancelMute();
};// --- 全屏切换 ---
const fullscreenSwich = () => {if (!jessibucaPlayer.value) return;const isFull = document.fullscreenElement ||document.msFullscreenElement ||document.mozFullScreenElement ||document.webkitFullscreenElement;jessibucaPlayer.value.setFullscreen(!isFull);fullscreen.value = !isFull;
};// --- 生命周期 ---
onMounted(() => {nextTick(() => {if (!props.videoUrl && route.params.url) {currentVideoUrl.value = decodeURIComponent(route.params.url);} else {currentVideoUrl.value = props.videoUrl || '';}if (currentVideoUrl.value) play(currentVideoUrl.value);});
});onBeforeUnmount(() => {destroy();
});
</script><style>
.buttons-box {width: 100%;height: 28px;background-color: rgba(43, 51, 63, 0.7);position: absolute;/* 保持绝对定位 */display: flex;justify-content: space-between;left: 0;bottom: 0;user-select: none;z-index: 999;/* 保证在播放器之上 */
}.jessibuca-btn {width: 20px;color: #fff;line-height: 27px;margin: 0px 10px;padding: 0 2px;cursor: pointer;text-align: center;font-size: 0.8rem !important;
}.buttons-box-right {position: absolute;right: 0;
}
</style>
组件使用:
<Player v-if="video" :videoUrl="video.ws_flv" :showButton="true" />