h265 flv.js组件封装Vue3
组件playVideo.vue
<!-- flv视频播放组件 -->
<template>
<div class="videoPlayer">
<!-- 标题 -->
<div class="title">
<span></span>
<span>{{ videoObj.title }}</span>
<span class="closeIco" @click="closeVideo(videoObj.videoObj.id)">×</span>
</div>
<!-- 视频 -->
<div class="videoBox">
<video @click="preventDefaultClick" muted playsinline :id="videoObj.videoObj.id"
class="video video-js vjs-default-skin vjs-big-play-centered" ref="videoElement" controlsList="nodownload"
disablePictureInPicture style="object-fit: fill !important;" controls></video>
</div>
<!-- 控制台 -->
<!-- <div class="controls"></div> -->
</div>
</template>
<script setup>
import { useCarStore } from '@/store/carStore';
import { nextTick } from 'vue';
const carStore = useCarStore();
const props = defineProps({
videoObj: {
type: Object,
default: () => {
return {};
},
},
});
// 获取 video 元素的引用
const videoElement = ref(null);
let flvPlayer = null;
let id = ref(null);
watch(() => props.videoObj,
async (val) => {
if (val.videoObj.id == id.value) return;
id.value = val.videoObj.id;
console.log("视频组件接收到", val);
// console.log("视频组件接收到", val.videoObj.url);
let url = val.videoObj.url;
// let url = "http://116.62.199.225:9980/httpflv.rplay.d390159f9576e142a6c0c5ff35f443bf.admin*5bd5ed5652c0c639fcc027dc90951fff.8802.1.0.1";
if (!url) return;
await nextTick();
setTimeout(() => {
initPlayer(url);
}, 1000);
},
{ immediate: true }
);
const initPlayer = (url) => {
// 先销毁旧实例
if (flvPlayer) {
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
flvPlayer = null;
}
if (!flvjs.isSupported()) {
console.error('FLV is not supported');
return;
}
flvPlayer = flvjs.createPlayer({
type: 'flv',
url: url,
cors: true,
isLive: true, //是否为直播流
hasAudio: false, //是否包含音频
hasVideo: true, //是否包含视频
autoplay: true, // 自动播放
stashInitialSize: 512 // 增大初始缓存
// muted: true, // 静音
// language: "zh-CN",
// controls: true, // 启用控件
// preload: "auto", // 自动加载
// errorDisplay: true, // 错误展示
// fluid: false, // 跟随外层容器变化大小
// loop: false, // 循环播放
// notSupportedMessage: "此视频暂无法播放,请稍后再试",
// userActions: {
// hotkeys: true, // 是否支持热键
// }
}, {
enableStashBuffer: true, // 强制启用缓存
autoCleanupSourceBuffer: false, // 关闭自动清理
stashSize: 2 * 1024 * 1024 // 设置2MB缓存区
// enableStashBuffer: false, //是否启用缓冲区
// fixAudioTimestampGap: false, //是否修复音频时间戳的间隙
// autoCleanupSourceBuffer: true, // 自动清理 SourceBuffer[2](@ref)
// autoCleanupMaxBackwardDuration: 30 // 延长清理间隔(单位秒)
});
flvPlayer.attachMediaElement(videoElement.value);
flvPlayer.load();
flvPlayer.play();
};
// 请求视频并设置 URL
// onMounted(() => {
// const url = "http://116.62.199.225:9980/httpflv.rplay.d390159f9576e142a6c0c5ff35f443bf.admin*5bd5ed5652c0c639fcc027dc90951fff.8802.1.0.1";
// if (url) {
// initPlayer(url);
// } else {
// console.error('Video URL is not provided');
// }
// });
// 销毁 flv.js 实例
onBeforeUnmount(() => {
if (flvPlayer) {
flvPlayer.pause();//暂停播放数据流
flvPlayer.unload();//取消数据流加载
flvPlayer.detachMediaElement();//将播放实例从节点中取出
flvPlayer.destroy();//销毁播放实例
flvPlayer = null;
}
});
// 点击关闭单个视频
const closeVideo = async (id) => {
// if (flvPlayer) {
// flvPlayer.pause();
// await new Promise(r => setTimeout(r, 300)); // 增加300ms延迟
// flvPlayer.unload();
// flvPlayer.detachMediaElement();
// flvPlayer.destroy();
// }
if (flvPlayer) {
try {
// 等待播放器状态稳定
await flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
await new Promise(resolve => setTimeout(resolve, 200)); // 缓冲区清理延迟
} catch (e) {
console.warn('Player cleanup warning:', e);
} finally {
flvPlayer.destroy();
flvPlayer = null;
}
}
nextTick(() => {
carStore.removeCarById(id);
});
};
// const closeVideo = (id) => {
// if (flvPlayer) {
// flvPlayer.pause(); // 暂停播放数据流
// flvPlayer.unload(); // 取消数据流加载
// flvPlayer.detachMediaElement(); // 将播放实例从节点中取出
// flvPlayer.destroy(); // 销毁播放实例
// flvPlayer = null;
// }
// nextTick(()=>{
// carStore.removeCarById(id);
// })
// };
// 点击视频阻止默认时间
const preventDefaultClick = (event) => {
event.preventDefault();
};
// onMounted(() => {
// console.log(6666, H265Player);
// });
// const currentPlayer = ref(null);
/**
* 数据部分
*/
// const data = reactive({
// id: null,
// currentPlayer: null,
// initNum: 0,
// player: null,
// options: {
// autoplay: true, // 自动播放
// muted: true, // 静音
// language: "zh-CN",
// controls: false, // 控制条
// preload: "auto", // 自动加载
// errorDisplay: true, // 错误展示
// fluid: false, // 跟随外层容器变化大小,跟随的是外层宽度
// loop: false, // 循环播放
// // width: "500px",
// // height: "500px",
// // controlBar: false, // 设为false不渲染控制条DOM元素,只设置controls为false虽然不展示,但是存在
// // textTrackDisplay: false, // 不渲染字幕相关DOM
// userActions: {
// hotkeys: true, // 是否支持热键
// },
// notSupportedMessage: "此视频暂无法播放,请稍后再试",
// // notSupportedMessage: "",
// // techOrder: ["h5", "flash"], // 定义Video.js技术首选的顺序
// sources: [
// {
// src: "", // 视频或者直播地址
// // src: "http://116.62.199.225:9980/httpflv.rplay.4328eea7-9ce9-4f3e-8731-69c344007546.admin*5bd5ed5652c0c639fcc027dc90951fff.1444.2.0.1", // 视频或者直播地址
// // type: "application/x-mpegURL",
// // type: "rtmp/flv"
// type: "video/x-flv"
// },
// ],
// },
// });
// const { id, player, options, currentPlayer } = toRefs(data);
// 初始化视频播放器
// const init = () => {
// if (player.value) {
// player.value.dispose();
// }
// try {
// player.value = videojs(
// currentPlayer.value,
// options.value,
// function onPlayerReady() {
// videojs.log(`Your player is ready!`);
// }
// );
// } catch (error) {
// console.error('Failed to initialize video player:', error);
// // 处理错误,例如显示错误信息给用户
// }
// };
// // 关闭视频
// const closeVideo = (id) => {
// if (player.value) {
// player.value.dispose();
// player.value = null;
// }
// carStore.removeCarById(id);
// };
// watch(() => props.videoObj,
// async (val) => {
// // if (player.value) {
// // player.value.dispose();
// // player.value = null;
// // }
// // console.log(111, val.videoObj.id);
// // console.log(222, id.value);
// // console.log(333, val.videoObj.id == id.value);
// if (val.videoObj.id == id.value) return;
// id.value = val.videoObj.id;
// // console.log("视频组件接收到", val.videoObj.id, id.value);
// let url = val.videoObj.url;
// // let url = "http://116.62.199.225:9980/httpflv.rplay.4328eea7-9ce9-4f3e-8731-69c344007546.admin*5bd5ed5652c0c639fcc027dc90951fff.1444.2.0.1";
// if (!url) return;
// if (val.videoObj.autoPlay) data.options.controls = true;
// if (val.videoObj.loop) data.options.loop = true;
// if (player.value) {
// // toRaw(player.value).src(url);
// // toRaw(player.value).src(url);
// player.value.src({ type: 'video/x-flv', src: url });
// } else {
// // console.log("视频组件初始化", url);
// options.value.sources[0].src = url;
// // setTimeout(() => {
// // 确保 DOM 更新
// await nextTick();
// setTimeout(() => {
// init();
// }, 0);
// // }, 700);
// }
// },
// { immediate: true }
// );
// onBeforeUnmount(() => {
// if (player.value) {
// player.value.dispose();
// player.value = null;
// }
// });
</script>
<style scoped lang="scss">
.videoPlayer {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0);
display: flex;
flex-flow: column;
&:hover {
.closeIco {
opacity: 1 !important;
}
}
video {
width: 100% !important;
height: 100% !important;
object-fit: fill !important;
}
video::-webkit-media-controls-play-button {
display: none !important;
}
video::-webkit-media-controls-timeline {
display: none !important;
}
.title {
height: vh(25);
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 vh(5);
background-color: #202020;
color: #fff;
z-index: 99;
span {
&:nth-child(2) {
font-size: vh(14);
white-space: nowrap;
/* 防止文本换行 */
overflow: hidden;
/* 隐藏溢出的文本 */
text-overflow: ellipsis;
/* 溢出文本显示为省略号 */
max-width: 100%;
/* 设置最大宽度 */
display: block;
}
&:nth-child(3) {
font-size: vh(25);
cursor: pointer;
opacity: 0;
&:hover {
transform: scale(1.1);
transition: all 0.1s;
}
}
}
}
.videoBox {
width: 100% !important;
height: calc(100% - vh(25)) !important;
// border: 3px solid pink !important;
// ::v-deep .vjs-tech {
// object-fit: fill !important;
// }
// // 播放按钮
// ::v-deep .vjs-tech ::-webkit-media-controls-play-button {
// display: none;
// }
// ::v-deep .vjs-tech ::-webkit-media-controls-timeline {
// display: none;
// }
// ::v-deep .vjs-modal-dialog-content {
// display: none !important;
// }
// ::v-deep .vjs-modal-dialog {
// display: none !important;
// }
// ::v-deep .vjs-control-bar {
// opacity: 1 !important;
// }
// ::v-deep .vjs-controls-disabled .vjs-control-bar,
// .vjs-using-native-controls .vjs-control-bar,
// .vjs-error .vjs-control-bar {
// display: block !important;
// }
}
// .controls {
// }
}
</style>
引入使用
<div class="container_center_body" ref="fullscreenDiv" v-if="videoList && videoList.length > 0">
<div class="videos" :class="`videos_${rowId}`" v-for="(item, index) in videoList" :key="index">
<videoPlayer v-if="item.id" :videoObj="{
title: item.allName,
player: null,
videoObj: {
id: item.id, // 确保 id 唯一
url: item.wsUrl,
// url: item.url,
autoPlay: true,
loop: true
}
}"></videoPlayer>
</div>
</div>
import videoPlayer from "@/components/playVideo/index.vue";
.html入口文件引入
<script type="module" src="/src/main.js"></script>
<script type="module" src="/flv/flv.min.js"></script>
资源文件下载:
→点击下载