主页 > 游戏开发  > 

uniapp中videojs、renderjs的使用

uniapp中videojs、renderjs的使用

在uniapp中使用了某些前端库或iframe,需要操作这些库中的dom的时候, 而uni上又没有document等基础对象。也就无法操作这些dom去实现一些交互逻辑,那么,涉及到这些的前端类库就无法使用,例如html2、canvas、image、video。而要用这些怎么办,这是用就出现了renderjs这种视图层工具来进行渲染。大幅降低逻辑层和视图层的通讯损耗,提供高性能视图交互能力

使用方法:

在原有script下方再添加script,其中lang="renderjs"固定, module=“demo”,module的名字任意起可以通过 this.$ownerInstance 获取当前组件的 ComponentDescriptor 实例。类似于vm视图层绑定事件通过 module名称 . 逻辑层定义方法,有两个参数,1.事件对象event,2. 当前组件实例ComponentDescriptor两个script间的通信需要通过:this.$ownerInstance 全局组件实例 或者 事件参数ComponentDescriptor 身上的callMethod方法,去抛出方法、传值,类似于vue组件间emit可以使用 vue 组件的生命周期不可以使用 App、Page 的生命周期

使用参考: blog.csdn.net/dabaooooq/article/details/129272111

代码

uniapp在APP端video层级最高,不能被其它覆盖,引入videojs实现,使用renderjs实现原生的DOM操作。

<template> <div class="full_screen_video" v-show="visible" :class="{ 'normal': !fullScreen }"> <!-- 遮盖video 添加点击事件 --> <div class="video_mask" @click="foldShortVideo" ></div> <p v-if="fullScreen" class="back" @click="closeDialogHandle"> <image src="@i/common/back_white.svg" alt=""></image> <span>{{ title}}</span> </p> <view :info="videoItem" :change:info="videos.updateVideo" :options="videoOptions" :change:options="videos.optionsChange" class="video_box" ref="videoEle" id="videoEle"> </view> <view class="video_control_box" v-if="fullScreen" > <!-- 开始结束时间 --> <view class="video_time"> <span>{{ formatTime(videoCurrentTime) }} </span> <span>{{ formatTime(videoTotalTime) }}</span> </view> <!-- 视频播放器 --> <div class="video_control"> <p class="disp_ac"> <image v-if="!isStart" src="@i/video/icon_video_start.svg" mode="widthFix" @tap="startAndStop('start')" alt=""></image> <image v-else src="@i/video/icon_video_stop.svg" mode="widthFix" @tap="startAndStop('stop')" alt=""></image> <image src="@i/video/icon_video_next.svg" mode="widthFix" @tap="playNext" alt="" v-if="videoList.length > 0"></image> </p> <p> <view :class="{'normal_mode':normalMode}" class="formation_select_box mr60" @click="showOption = !showOption"> <span class="select_text">{{ speedValue +'x'}}</span> <view class="select_menu" :class="{ ani: showOption }" v-show="showOption"> <view class="select_menu_item" v-for="item in speedOptions" :key="item.value"> <span :class="{'active': speedValue === item.value}" @click="changeSpeed(item.value)">{{ item.label +'x'}}</span> </view> </view> </view> <image v-if="normalMode" src="@i/video/full_screen.svg" mode="widthFix" @tap="fullScreenVideo" alt=""></image> </p> </div> </view> <view v-else class="video_control_box video_control_box_small" > <div class="time_bar_box"> <div class="time_btn"> <image v-if="!isStart" src="@i/video/icon_video_start.svg" mode="widthFix" @tap="startAndStop('start')" alt=""></image> <image v-else src="@i/video/icon_video_stop.svg" mode="widthFix" @tap="startAndStop('stop')" alt=""></image> <image src="@i/video/icon_video_next.svg" mode="widthFix" @tap="playNext" alt="" v-if="videoList.length > 0"></image> </div> <div class="time_bar"> <span>{{ formatTime(videoCurrentTime) }} </span> <span>{{ formatTime(videoTotalTime) }}</span> </div> </div> <div class="video_btn"> <view class="formation_select_box mr50" @click="showOption = !showOption"> <span class="select_text">{{ speedValue +'x'}}</span> <view class="select_menu" :class="{ ani: showOption }" v-show="showOption"> <view class="select_menu_item" v-for="item in speedOptions" :key="item.value"> <span :class="{'active': speedValue === item.value}" @click="changeSpeed(item.value)">{{ item.label +'x'}}</span> </view> </view> </view> <image src="@i/video/download.svg" mode="widthFix" @tap="downloadVideo" alt=""></image> <image v-if="normalMode" src="@i/video/full_screen.png" mode="widthFix" @tap="fullScreenVideo" alt=""></image> </div> </view> <!-- 视频列表 --> <div class="video_list" v-if="showVideoList && videoList.length" > <scroll-view scroll-x="true" > <div class="video_list_item"> <div v-for="(item, index) in videoList" :key="index" class="video_item" :class="{'active': index === currentVideoIndex}" @click="playVideo(item, index)"> <div class="video_img"> <image class="snapshot" :src="item.snapshot && !item.imgError ? item.snapshot : defaultSnapshot" @error="item.imgError = true" alt="snapshot" ></image> <span class="total_time" v-if="item.end && item.start"> {{formatTime(item.end - item.start)}} </span> </div> <div class="clip_info"> <div class="text_over">{{item.name || item.label || ''}}</div> <div class="video_list_time">{{ formatDate(item.createTime).split('-').slice(0,3).join('/').split(' ').slice(0,1).join() }}</div> </div> </div> </div> </scroll-view> </div> </div> </template> <script> export default { name: 'pad-video-play', // 在主视频内播放的组件,短片会跳转到主视频对应的时间 props: { // 控制显示隐藏 visible: { type: Boolean, default: false }, // 主视频下的短视频播放列表 videoList: { type: Array, default:() => [] }, // 主视频播放源 videoMain: { type: Object, default: () => { } }, // 视频类型, 默认为全屏播放 videoType: { type: String, default: 'full_screen' }, }, data() { return { videoCurrentTime: 0, // 视频当前时间 videoTotalTime: 0, // 视频总时长 isStart: false, // 是否开始 volumeValue: 1, // 音量初始值(0-1) showVideoList: true, // 是否显示视频列表 currentVideoIndex: -1, // 当前播放的视频下标 defaultSnapshot: require("static/images/video/default_snapshot.svg"), // 默认缩略图 videoOptions: { closeDialog: false }, // 视频操作选项 fullScreen: this.videoType === 'full_screen', // 是否是全屏 speedValue: '1.0', // 初始播放速度 showOption:false, // 控制倍速弹窗 speedOptions: [ // 阵型选项 { label: '2.0', labelEn: '2.0', value: '2.0' }, { label: '1.5', labelEn: '1.5', value: '1.5' }, { label: '1.25', labelEn: '1.25', value: '1.25' }, { label: '1.0', labelEn: '1.0', value: '1.0' }, { label: '0.5', labelEn: '0.5', value: '0.5' }, ], videoItem: {}, title:'' }; }, computed: { // 是否是正常模式下的视频播放 normalMode() { let boolean = this.videoType === 'normal'; return boolean; } }, watch: { currentVideoIndex(newVal) { this.$emit('change-video-index', newVal); }, videoMain: { handler (newVal) { this.videoItem = { url: newVal.url } this.title = newVal.name }, immediate: true, deep:true }, visible(newVal) { if (newVal) { this.videoOptions = { closeDialog: false, volumeChangeOption: { volume: 1 } } this.volumeValue = 1; } } }, methods: { foldShortVideo(){ this.showVideoList = !this.showVideoList }, // 关闭弹框 closeDialogHandle () { this.videoOptions = { closeDialog: true } if (this.videoType === 'full_screen') { // setTimeout(() => { this.$emit('cancelVideo', false); // },100) } else { this.fullScreen = false; } this.showVideoList = false; this.$emit('update:visible',false) }, // 开始视频 onPlayerPlay() { this.speedValue = '1.0' this.isStart = true; }, // 实时更新 onTimeUpdate(currentTime){ this.videoCurrentTime = currentTime; }, // 暂停视频 onPlayerPause() { this.isStart = false; }, // 加载视频源数据 onLoadedmetadata(totalTime) { this.videoTotalTime = totalTime; }, // 开始或暂停视频 startAndStop(type) { this.videoOptions = { startAndStopOption: { type } } }, // 改变视频音量 volumeChange(volume) { this.volumeValue = volume; this.videoOptions = { volumeChangeOption: { volume } } }, // 播放视频 playVideo(item, index) { this.speedValue = '1.0' // // 在正常模式播放下, 不可重复点击当前视频 if (this.normalMode && item && this.currentVideoIndex === index) { return; } this.$set(this.videoItem, 'startTime', item.start); this.currentVideoIndex = index; this.videoList.forEach(item => { item.forceUpdate = false }) item.forceUpdate = true }, // 改变视频的速度 changeSpeed(speed) { this.speedValue = speed || '1.0'; this.videoOptions = { playbackRate: { speed } } }, // 播放下一个 playNext(){ this.speedValue = '1.0' this.videoList.forEach(item => { item.forceUpdate = false }) let index = this.currentVideoIndex; if(this.currentVideoIndex+1<this.videoList.length){ this.currentVideoIndex = index+1; this.$set(this.videoList[index+1],'forceUpdate',true) this.$set(this.videoList, index + 1, this.videoList[index + 1]); this.$set(this.videoItem, 'startTime', this.videoList[index+1].start); }else{ this.currentVideoIndex = 0; this.$set(this.videoList[0],'forceUpdate',true) this.$set(this.videoList, 0, this.videoList[0]); this.$set(this.videoItem, 'startTime', this.videoList[0].start); } }, // 进入/退出 全屏视频 fullScreenVideo() { this.fullScreen = !this.fullScreen; }, // 格式化时间 formatTime(result) { let h = Math.floor(result / 3600) < 10 ? "0" + Math.floor(result / 3600) : Math.floor(result / 3600); let m = Math.floor((result / 60) % 60) < 10 ? "0" + Math.floor((result / 60) % 60) : Math.floor((result / 60) % 60); let s = Math.floor(result % 60) < 10 ? "0" + Math.floor(result % 60) : Math.floor(result % 60); return Math.floor(result / 3600) == 0 ? m + ":" + s : h + ":" + m + ":" + s; }, // 格式化日期 formatDate(createTime){ let date = new Date(createTime); let str = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; return str; } } }; </script> <script module="videos" lang="renderjs"> import './video-offset.js'; // videojs 引入 import Videojs from 'video.js'; import 'video.js/dist/video-js.css'; export default { data() { return { videoPlayer: null, // 当前视频播放器 } }, mounted() { }, methods: { // 初始化视频 initVideoJS(item) { if(this.videoPlayer){ this.videoPlayer.dispose(); this.videoPlayer = null; } let that = this; let videoEle = document.createElement('video'); videoEle.style = 'width:100%; height:100%'; videoEle.setAttribute("class", "video-js vjs-big-play-centered"); let videos = document.getElementById('videoEle'); console.log(videos); videos.appendChild(videoEle) let option = { controls: true, // 是否显示控制条 preload: 'auto', // 是否预加载视频数据 muted: false, // 是否静音 language: 'zh-CN', // 设置语言 autoplay: true, // 自动播放, 正常模式下不进行自动播放 sources: [ // 视频源 { type: "video/mp4", src: item.url }, { type: "video/webm",// webm格式 src: item.url }, { type: "video/mov", // mov格式 src: item.url }, ], controlBar: { // 设置控制条组件 children: [ { name: 'progressControl' }, // 播放进度条 // // { name: 'fullscreenToggle' } // 全屏按钮 ] } }; // video.js初始化实例化的对象 this.videoPlayer = Videojs(videoEle, option, function onPlayerReady() { // 开始视频 this.on('play', function() { that.$ownerInstance.callMethod('onPlayerPlay'); }) // 暂停视频 this.on('pause', function() { that.$ownerInstance.callMethod('onPlayerPause'); }) // 实时更新 this.on('timeupdate', function() { let currentTime = that.videoPlayer ? that.videoPlayer.currentTime() : 0; that.$ownerInstance.callMethod('onTimeUpdate', currentTime); }) // 加载视频源数据 this.on('loadedmetadata', function() { let totalTime = parseInt(that.videoPlayer.duration()) || 0; that.$ownerInstance.callMethod('onLoadedmetadata', totalTime); }) }); }, // 监听 videoData 数据变更 updateVideo(newValue) { if(!newValue || !newValue.url) return; if(this.videoPlayer && newValue.startTime){ this.videoPlayer.currentTime(newValue.startTime) return } this.initVideoJS(newValue); }, // 监听改变视频操作选项 optionsChange(newValue) { if (newValue && newValue.startAndStopOption) { // 开始或暂停视频 let { type } = newValue.startAndStopOption; type === 'start' ? this.videoPlayer.play() : this.videoPlayer.pause(); } else if (newValue && newValue.volumeChangeOption) { // 改变视频音量 let { volume } = newValue.volumeChangeOption; this.videoPlayer && this.videoPlayer.volume(volume); } else if (newValue && newValue.closeDialog) { console.log('销毁了') // 销毁videoJs实例 this.videoPlayer && this.videoPlayer.dispose(); this.videoPlayer = null } else if (newValue && newValue.continuePlay) { // 继续播放视频 let { url } = newValue.continuePlay; this.initVideoJS(url); } else if(newValue && newValue.playbackRate){ let { speed } = newValue.playbackRate; // 改变视频的播放速度 this.videoPlayer && this.videoPlayer.playbackRate(speed); } } } } </script> <style lang='less' scoped> .video_mask { position: absolute; width: 100%; height: calc(100% - 62.81rpx); top: 0; left: 0; background-color: transparent; z-index: 1; } ::v-deep { // 视频style .video_box{ width: 100%; height: 100%; video{ object-fit:fill; } } .vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{ opacity: 1; } .video-js{ position: relative; .vjs-control-bar { width:663.32rpx; left: 43.34rpx; bottom: 47.9rpx; background-color: transparent; z-index: 105; opacity: 1; } .vjs-play-progress{ background: #38CB89; &::before{ color: #38CB89; } } .vjs-slider{ background-color: rgba(255, 255, 255, 0.36); } } } .disp_ac{ display: flex; align-items: center; } /* 文字超出部分设置为... */ .text_over { max-width: 85%; word-break: break-all; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; /* 这里是超出几行省略 */ overflow: hidden; } .full_screen_video{ width: 100%; height: 100%; position: fixed; z-index: 102; left: 0; top: 0; box-sizing: border-box; color: #fff; font-family: 'SysFontR'; // 默认视频样式 &.normal{ width: 100%; height: 100%; position: relative; .video_list,.video_shrink{ display: none; } .video_control_box{ width: 100%; height: 37.69rpx; position: absolute; left: 0; bottom: 4.71rpx; z-index: 5; display: flex; flex-direction: column; align-items: center; padding: 0 10.05rpx; box-sizing: border-box; &.video_control_box_small{ .formation_select_box{ bottom: 7rpx; .select_text{ font-size: 10.05rpx; } .select_menu{ bottom:0; transform:scale(.8) } } } .time_bar_box{ width: 100%; height: 12.25rpx; display: flex; align-items: center; .time_btn{ image{ width: 11.93rpx; height: 9.42rpx; margin-right: 10.05rpx; } } .time_bar{ flex: 1; display: flex; justify-content:space-between; font-size: 9.74rpx; color: #f3f3f3; } } .video_btn{ width: 100%; display: flex; justify-content: flex-end; text-align: right; margin-top: 6.28rpx; image{ width: 10.99rpx; height: 10.05rpx; margin-right: 15.7rpx; } } } .video_control{ height: 47.11rpx; bottom: 4.71rpx; image{ width: 10.05rpx !important; height: 10.05rpx !important; margin-right: 15.7rpx; } } ::v-deep { .vjs-control-bar{ width: calc(100% - 74.75rpx - 37.69rpx); left: 76.75rpx; bottom: 27.84rpx; } } } .back{ position: absolute; z-index: 200; top: 0; left: 0; width: 100%; height: 58.59rpx; padding-left: 14.64rpx; font-size: 13.82rpx; display: flex; align-items: center; cursor: pointer; background: linear-gradient(180deg, #000000 0%, rgba(0, 0, 0, 0) 100%); image{ width: 6.44rpx; height: 12.3rpx; margin-right: 20.1rpx; } } .formation_select_box{ position: relative; border-radius: 3.77rpx; display: flex; align-items: center; justify-content: center; &.normal_mode{ right: 118rpx; } .select_text{ font-family: 'AkrobatMedium'; font-size: 11.31rpx; color: #fff; position: relative; } .select_menu { width: 43.66rpx; position: absolute; bottom: 18.04rpx; left: -13rpx; z-index: 999; padding: 13.82rpx 0 0; text-align: center; box-sizing: border-box; border-radius: 3.77rpx; background: #000000; color: #F3F3F3; .select_menu_item { position: relative; z-index: 2; margin-bottom: 12.56rpx; >span { display: inline-block; width: 100%; height: 100%; box-sizing: border-box; font-size: 10.05rpx; font-family: AkrobatRegular; &.active{ color: #38CB89; } } } } .ani { animation: ani 0.2s; } @keyframes ani { 0% { transform: scaleY(0); } 100% { transform: scaleY(1); } } .mask_box { width: 70vw; height: 100vh; position: fixed; left: 0; top: 60rpx; z-index: 99; background: transparent; } } .video_time{ position: absolute; bottom: 49.94rpx; width: 100%; box-sizing: border-box; padding:0 15.7rpx; line-height: 14.13rpx; display: flex; justify-content:space-between; z-index: 104; } .video_control{ position: absolute; left: 0; z-index: 5; bottom: 5.85rpx; width: 161.13rpx; height: 35.15rpx; display: flex; align-items: center; justify-content: space-between; padding: 0 19.04rpx; box-sizing: border-box; width: 100%; p{ display:flex; align-items: center; image{ width: 17.59rpx; height: 17.59rpx; margin-right: 15.23rpx; // opacity: .6; } .video_volume{ position: relative; .video_volume_slider{ display: none; position: absolute; right: 30px; top: -85px; padding-top: 5px; background-color: rgba(0, 0, 0, 0.36); &.hover{ display: inline-block; } } } } } .video_list{ position: absolute; width: 100%; top: 0; right: 0; z-index: 201; background: rgba(29, 29, 29, 0.8); backdrop-filter: blur(50px); color: #fff; padding-top: 25.13rpx; padding-bottom: 6.28rpx; box-sizing: border-box; display: flex; // transition: all 200ms; .close_img{ position: absolute; top: 50%; transform: translateY(-50%); right: 236.81rpx; width: 20.5rpx; height: 75rpx; } .video_item{ width: 83.54rpx; flex-shrink: 0; display: flex; flex-direction: column; margin-left: 12.56rpx; box-sizing: border-box; display: flex; &.active{ .text_over{ font-family: SysFontM; color: #38CB89; } } .video_img{ width: 100%; height: 47.11rpx; box-sizing: border-box; position: relative; image.snapshot{ border-radius: 3.77rpx; width: 100%; height: 100%; } .total_time{ font-family: SysFontR; height: 12.89rpx; position: absolute; left: 2.92rpx; bottom: 2.92rpx; padding: 0 5.85rpx; font-size: 8.2rpx; line-height: 12.89rpx; color: rgb(255, 255, 255); background: rgba(30, 30, 30, .6); } } .clip_info{ width: 100%; box-sizing: border-box; font-size: 8.2rpx; color: rgba(255, 255, 255, .8); position: relative; padding: 4.71rpx 0; // .clip_label{ // font-size: 10.68rpx; // color: rgba(255, 255, 255,1); // line-height: 14.06rpx; // margin-bottom: 7.54rpx; // display: flex; // align-items: center; // } } } } .video_list_item { display: flex; width: 100%; overflow-x: auto; } .video_shrink{ position: fixed; top: 50%; transform: translateY(-50%); right: 0; width: 20.5rpx; height: 75rpx; } .video_list_time { font-family: AkrobatRegular; margin-top: 3px; } } </style>;
标签:

uniapp中videojs、renderjs的使用由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“uniapp中videojs、renderjs的使用