vue3下载第三方插件videojs,达到播放器的效果,并且点击事件能够动态更改播放器的src。实现思路:
-
场景一:只有一个播放器,当点击事件,直接赋值,动态更改封装好的组件的src参数,能够实现切换播放器的效果。
-
场景二:不止有一个窗口,而是多窗口,要选中多个窗口中的一个窗口,然后点击,达到动态切换选中播放器的src。
二、实现总结
1.vue框架更改对应数据data,对应的视图view变化。
2.在封装的组件监听传参src的变化,如果点击的 src和之前的不同,进行切换资源,播放器重新加载播放;
3.播放器的资源source的参数有src+type,注意type可以不设置。
推荐用法:不设置type,如果要设置一定要更传过来的视频流的数据格式保持一致,否则会爆红
。
三、效果展示
四、完整代码
(一)、封装组件
组件封装,videojs封装成一个组件:
完整代码:
<script setup lang="ts">
import { computed, CSSProperties, onMounted, ref, watch } from 'vue'
import videojs from 'video.js'
import type { VideoJsPlayerOptions } from 'video.js'
import 'video.js/dist/video-js.min.css'
type MyVideoProps = {
/** 视频地址 */
src: string
width?: string
height?: string
const props = withDefaults(defineProps<MyVideoProps>(), {})
// video标签
const videoRef = ref<HTMLElement | null>(null)
// video实例对象
let videoPlayer: videojs.Player | null = null
const videoWrapStyles = computed<CSSProperties>(() => {
return {
width: props.width || '100%',
height: props.height || '100%'
// 初始化videojs
const initVideo = () => {
// https://gitcode.gitcode.host/docs-cn/video.js-docs-cn/docs/guides/options.html
const options: VideoJsPlayerOptions = {
language: 'zh-CN', // 设置语言
controls: true, // 是否显示控制条
preload: 'auto', // 预加载
autoplay: true, // 是否自动播放
fluid: false, // 自适应宽高
src: props.src, // 要嵌入的视频源的源 URL
objectFit: 'cover', // 同css object-fit,作用于video标签
notSupportedMessage: 'Ajiang此视频暂无法播放,请稍后再试' //允许覆盖Video.js无法播放媒体源时显示的默认信息。
if (videoRef.value) {
// 创建 video 实例
videoPlayer = videojs(videoRef.value, options, onPlayerReady)
// video初始化完成的回调函数
const onPlayerReady = () => { }
onMounted(() => {
initVideo()
watch(() => props.src, (now) => {
if (now) {
videoPlayer.pause()
// videoPlayer.dispose()
videoPlayer.reset()
setTimeout(() => {
videoPlayer.src([
src: props.src,
// type: "application/x-mpegURL" //type可以不写,一旦写了一定要符合否则报错
videoPlayer.load()
videoPlayer.play()
}, 10)
</script>
<template>
<div :style="videoWrapStyles">
<video id="my-player" ref="videoRef" class="video-js w-full h-full">
<source :src="src" />
</video>
</template>
<style lang="less" scoped>
.w-full {
width: 100%;
.h-full {
height: 100%;
</style>
(二)、局部引入
页面引入,引入封装的组件:
完整代码:
<template>
<div class="home_box">
<div class="leftBox">
<div class="top_nav">
<div v-for="(item, index) in topnav" :key="index" :class="item.class_name" @click="toOtherModel(item.path)">
{{ item.name }}
<div class="videoBox">
<div :class="data.cruentIndex == 0 ? 'playerBox1' :
data.cruentIndex == 1 ? 'playerBox2' :
data.cruentIndex == 2 ? 'playerBox3' : ''
<template v-if="data.cruentIndex == 0 && data.videoUrlData">
<div class="myVedioBox">
<Vediojs :src="data.videoUrlData[0].url" width="100%" height="78vh" />
</template>
<template v-if="data.cruentIndex == 1">
<div class="myVedioBox" v-for="(item, index) in fourBoxData[1].controlTypy" :id="'player_' + index"
:key="index" @click="changePlayer(index)">
<Vediojs :src="data.videoUrlData[index]?.url" />
</template>
<template v-if="data.cruentIndex == 2">
<div class="myVedioBox" v-for="(item, index) in fourBoxData[2].controlTypy" :id="'player_' + index"
:key="index" @click="changePlayer(index)">
<Vediojs :src="data.videoUrlData[index]?.url" />
</template>
<div :class="data.cruentIndex == 3 ? 'playerBox4' : ''" v-show="data.isCloseed == true">
<template v-if="data.cruentIndex == 3">
<div class="myVedioBox" v-for="(item, index) in fourBoxData[3].controlTypy" :key="index">
<Vediojs :src="data.videoUrlData[index].url" width="100%" height="88vh" />
<div class="closeBox" @click="handleCloseBtn">关闭</div>
</template>
<div class="rightBox">
<div class="rightTitleBox">
<img :src="getAssetsFile('pmsHome/title_icon.png')" alt="一张图" />
<span>选择窗口</span>
<div class="fourBox">
<div class="fourInnerBox" v-for="(item, index) in fourBoxData" @click="handleImageIcon(index)">
<img :src="getAssetsFile(data.cruentIndex == index ? item.activeImgUrl : item.imgUrl)" alt="一张图">
<div class="rightTitleBox">
<img :src="getAssetsFile('pmsHome/title_icon.png')" alt="一张图" />
<span>监控列表</span>
<div class="rightTreeBox">
<div class="treeInnerBox">
<el-tree ref="treeRef" node-key="myTreeKey" :data="treeData" :props="treeDefaultProps"
@node-click="handleNodeClick" :current-node-key='4' default-expand-all :highlight-current="true" />
</template>
<script setup>
import { nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { getAssetsFile } from '@/utils'
import Vediojs from "./common/Vediojs.vue";
import 'video.js/dist/video-js.min.css'
const router = useRouter();
const treeRef = ref()
const treeDefaultProps = {
children: 'children',
label: 'label',
id: 'myTreeKey',
url: 'url'
onMounted(() => {
const topnav = reactive([
name: "360゜全景影像",
class_name: "panoramicImage_title",
path: "panoramicImage"
name: "综合感知",
class_name: "comprehensivePerception_title",
path: "comprehensivePerception"
const data = reactive({
cruentIndex: 0,
videoUrlData: [
id: '1',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '2',
label: '常规监控2',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
id: '3',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '4',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '5',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '6',
label: '常规监控2',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
id: '7',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '8',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '9',
label: '常规监控',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
isCloseed: false,
const fourBoxData = reactive([
imgUrl: 'intelligentNavigation/rightIcon/icon1.png',
activeImgUrl: 'intelligentNavigation/rightIcon/icon1_isChecked.png',
controlTypy: 1,
imgUrl: 'intelligentNavigation/rightIcon/icon2.png',
activeImgUrl: 'intelligentNavigation/rightIcon/icon2_isChecked.png',
controlTypy: 4,
imgUrl: 'intelligentNavigation/rightIcon/icon3.png',
activeImgUrl: 'intelligentNavigation/rightIcon/icon3_isChecked.png',
controlTypy: 9,
imgUrl: 'intelligentNavigation/rightIcon/icon4.png',
activeImgUrl: 'intelligentNavigation/rightIcon/icon3_isChecked.png',
controlTypy: 1,
const treeData = [
id: '1',
label: '常规监控',
url: '#',
children: [
id: '2',
label: '监控器1',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '3',
label: '监控器2',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
id: '4',
label: '监控器3',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '5',
label: '监控器4',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
id: '6',
label: '监控器集群',
children: [
id: '8',
label: '监控器5',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '9',
label: '监控器6',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
id: '10',
label: '监控器7',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '7',
label: '监控器集群二',
url: '#',
children: [
id: '11',
label: '监控器1',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
id: '12',
label: '监控器2',
url: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
id: '13',
label: '监控器3',
url: 'http://vjs.zencdn.net/v/oceans.mp4',
const changePlayer = (id) => {
console.log('ddd>>>', id, typeof id);
//2.如何获取到多个播放器中的一个?
data.currentPlayerId = id
const toOtherModel = (path) => {
router.push({ name: path });
const handleNodeClick = (treeNode, node) => {
if (node.level !== 1) {
defaultHighlight(treeNode.myTreeKey)
if (data.cruentIndex !== 0) {
data.videoUrlData[data.currentPlayerId].url = treeNode.url
} else {
data.videoUrlData[0].url = treeNode.url
} else {
console.log('高亮在根元素>>>');
const handleImageIcon = (value) => {
data.cruentIndex = value
if (value == 3) {
data.isCloseed = !data.isCloseed
const defaultHighlight = (param) => {
nextTick(() => {
treeRef.value.setCurrentKey(param) //树状图选中效果
const handleCloseBtn = () => {
data.isCloseed = !data.isCloseed
data.cruentIndex = 0
</script>
<style scoped lang='less'>
.home_box {
width: 100%;
height: 88vh;
display: flex;
position: relative;
.leftBox {
width: 80%;
height: 100%;
margin-right: 1%;
display: flex;
flex-direction: column;
.top_nav {
width: 100%;
height: 6%;
margin-bottom: 12px;
color: rgba(106, 151, 218, 1);
font-weight: bold;
font-size: calc(100vw * 22 / 1920);
display: flex;
>div {
height: 46px;
margin-right: 30px;
text-align: center;
line-height: 43px;
.panoramicImage_title {
width: 235px;
background: url("@/assets/images/intelligentNavigation/panoramicImage_title.png");
background-size: 100% 100%;
background-position: center;
.comprehensivePerception_title {
width: 185px;
background: url("@/assets/images/intelligentNavigation/comprehensivePerception_title.png");
background-size: 100% 100%;
background-position: center;
.videoBox {
width: 100%;
flex: 1;
background-color: #EBF1F6;
border: 1px solid #95DEF7;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
.video-player-wrapper {
width: 100%;
height: 100%;
background-color: pink;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.video-player-item {
width: 500px;
height: 300px;
background-color: skyblue;
.playerBox1 {
width: 98%;
height: 96%;
.myvideoBox {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
img {
width: 1400px;
height: 700px;
object-fit: cover;
.playerBox2 {
width: 98%;
height: 96%;
display: flex;
flex-wrap: wrap;
.myVedioBox {
width: 49%;
margin: 0px auto 8px auto;
img {
width: 100%;
height: 100%;
object-fit: cover;
.playerBox3 {
width: 98%;
height: 96%;
display: flex;
flex-wrap: wrap;
.myVedioBox {
width: 33%;
margin: 0px auto 7px auto;
img {
width: 100%;
height: 100%;
object-fit: cover;
.playerBox4 {
width: 100%;
height: 100%;
position: absolute;
top: 0px;
left: 0px;
z-index: 2;
.myVedioBox {
.closeBox {
width: 40px;
height: 40px;
line-height: 40px;
border: 1px solid #fff;
border-radius: 50%;
color: #4279CA;
font-weight: 600;
background-color: #fff;
position: absolute;
top: 10px;
right: 20px;
z-index: 3;
&:hover {
border: 1px solid #4279CA;
color: #fff;
background-color: #4279CA;
cursor: pointer;
.rightBox {
flex: 1;
display: flex;
flex-direction: column;
.rightTitleBox {
width: 100%;
height: 46px;
background: url('@/assets/images/intelligentNavigation/rightTitle_bg_long.png');
background-size: 100% 100%;
margin-bottom: 12px;
font-size: calc(100vw * 24 / 1920);
color: #4279CA;
font-weight: bold;
display: flex;
justify-content: flex-start;
align-items: center;
img {
width: 27px;
height: 27px;
margin: 8px 11px 8px 18px;
.fourBox {
width: 100%;
height: 64px;
background-color: #EBF0F5;
border: 3px solid #fff;
border-radius: 4px;
margin-bottom: 16px;
display: flex;
justify-content: space-around;
align-items: center;
box-sizing: border-box;
.fourInnerBox {
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
img {
width: 40px;
height: 40px;
object-fit: cover;
.rightTreeBox {
width: 100%;
flex: 1;
padding-left: 16px;
background-color: #EBF1F6;
border: 3px solid #fff;
border-radius: 4px;
box-sizing: border-box;
.treeInnerBox {
width: 100%;
height: 100%;
:deep(.el-tree) {
background-color: #EBF0F5;
</style>
<style lang='less'>
@import "@/assets/css/reset.less";
</style>
五、核心代码
封装组件的监听watch:
watch(() => props.src, (now) => {
if (now) {
videoPlayer.pause()
// videoPlayer.dispose()
videoPlayer.reset()
setTimeout(() => {
videoPlayer.src([
src: props.src,
// type: "application/x-mpegURL" //type可以不写,一旦写了一定要符合否则报错
videoPlayer.load()
videoPlayer.play()
}, 10)
局部引入的点击事件:
const changePlayer = (id) => {
console.log('ddd>>>', id, typeof id);
//2.如何获取到多个播放器中的一个?
data.currentPlayerId = id
const handleNodeClick = (treeNode, node) => {
if (node.level !== 1) {
if (data.cruentIndex !== 0) {
data.videoUrlData[data.currentPlayerId].url = treeNode.url //变data,变视频
} else {
data.videoUrlData[0].url = treeNode.url
} else {
console.log('高亮在根元素>>>');
1.vue框架更改对应数据data,对应的视图view变化。2.在封装的组件监听传参src的变化,如果点击的 src和之前的不同,进行切换资源,播放器重新加载播放;3.播放器的资源source的参数有src+type,注意type可以不设置。推荐用法:不设置type,如果要设置一定要更传过来的视频流的数据格式保持一致,否则会爆红。
npm install --save video.js
npm install --save videojs-contrib-hls
然后,我们需要引入videojs的css文件,例如在main.js中引入
import 'video.js/dist/video-js.css'
接着,我们在需要播放视频的页面引入js对象
import videojs from 'video.js'
import 'videojs-contrib-hls'
指定一个video容器,例如:
<video id=my-video class=
单页面引用
import "
video.
js/dist/
video-
js.css";
import {
videoPlayer } from "
vue-
video-player";
import
VideoPlayerOption from "@/app/com/bean/
VideoPlayerOption";
<div class="
video">
<
video-player
class="
video-player v
js-custom-skin"
解决video动态切换src视频不改变
1、问题:
在react项目中,有个需求是要动态的改变video中的视频,但是通过查看,video标签中的src已经更新但是视频还是原来的视频,并未更新。
2、百度产生原因:
当 video 中存在 source 标签的时候,浏览器渲染之后会自动去获取地址,即便地址改变,浏览器也不会再去获取地址。
3、有效的解决方案:
1、不使用source标签,直接在video中使用src,动态切换这里面的src
<video src={'http://' + item.vi
关于video.js
video.js是一个拥有h5背景的网络视频播放器,同时支持h5以及Flash视频播放(可加载本地静态资源视频以及网络链接视频)
官网:videojs.com/
npm install video.js
main.js中引入
import Video from 'video.js'
import 'video.js/dist/video-js.css'
Vue.prototype.$video = Video
<video id=myVideo
class=video-js vjs-def
今天被这个关于m3u8视频播放不了搞了一下午,这个项目所有的视频流都是m3u8格式的,后台给我们返回的都是m3u8格式的视频流,解决了好长时间,看了好多博客,只有这个博客给我点启发,去解决这个问题,请查看。会使用两种方法来解决这个问题
第一种方法
1.在
vue中安装下面这些插件
cnpm install
video.
js --save
ccnp install
videojs-contrib-hls --save
网上有说
video.
js版本太高不能使用,不能使用7以上的版本,用5版本的,最后结果测试,根本不需要什么低版本的高版本的,7版本以上的没有任何问题。小编已经亲自试验过
this.current
VideoIndex = index;
// 调用
Video.
js 播放当前视频
videojs('my-
video').
src(this.playlist[index].
src);
3. 在 HTML 中使用
Video.
js 创建视频
播放器,并绑定当前播放视频的索引。
```html
<div id="app">
<
video id="my-
video" class="
video-
js v
js-default-skin" controls preload="auto" width="640" height="264">
<!--
Video.
js 设置 -->
</
video>
<li v-for="(
video, index) in playlist" :key="index" @click="play
Video(index)">
{{
video.title }}
在上述示例中,点击播放列表中的视频标题时,会调用 `play
Video` 方法来更新当前播放的视频索引,并使用
Video.
js 播放对应的视频。
请注意,上述示例仅为演示目的,实际使用时可能需要根据具体需求进行适当的修改和扩展。
VIDEOJS: ERROR: (CODE:4 MEDIA_ERR_SRC_NOT_SUPPORTED) The media could not be loaded, either because t
VIDEOJS: ERROR: (CODE:4 MEDIA_ERR_SRC_NOT_SUPPORTED) The media could not be loaded, either because t
videojs 播放视频
geoserver编辑样式 【开发工具QGis的初次使用】