uniapp APP端视频轮播问题(黑屏,变形)
今天接到一个需求:需要在商品详情轮播展示视频,本以为很简单,但是发现好多坑,在app端出现黑屏,卡顿,变形,视频只展示半屏的情况。
完整代码放底下了
问题1:有声音但是黑屏(所有端都出现)
原因:没有传入poster(封面图)参数,前提 controls 必须为true
问题2:APP端 视频轮播出现变形,走移,卡顿,展示半屏问题
原因:video属于原生标签,层级高,普通标签无法覆盖,在swiper中嵌套video,会受到scroll-view的影响,视频发生错位。
解决方案:大概方案:使用cover-view,在不播放视频以及切换轮播时用cover-view展示封面图,来代替video标签滑动
经过借鉴了一些前辈的代码调好了。特此记录一下
效果展示
完整代码如下
复制即可用
因为视频 和轮播 代码量比较大,把video单独用子组件装起来
//展示页面
<my-swiper :list="bannerList" :height="height"></my-swiper>
import MySwiper from '@/components/my-swiper/index';
//bannerList:参数 height:轮播高度
[{
poster: "http://oos-cn.ctyunapi.cn/ebusiness/member/1657525898058.jpg",
src: "http://oos-cn.ctyunapi.cn/ebusiness/member/1659077187102.mp4",
type: "video"
},
{src: "http://oos-cn.ctyunapi.cn/ebusiness/member/1657525559014.jpg",
type: "image"
}]
components/my-swiper/index
<template>
<view class="swiperContent">
<swiper
@transition='transition'
@change="change"
@animationfinish="animationfinish"
:indicator-dots="indicatorDots"
:indicator-active-color="indicatorActiveColor"
:indicator-color="indicatorColor"
:autoplay="autoplay && !videoPlaySataus"
:current="activeCurrent"
:interval="interval"
:vertical="vertical"
:style="{'height':swiperHeight+'px'}"
class="z-screen-swiper">
<swiper-item
class="z-swiper-item"
v-for="(item,index) in swiperList"
:key="index"
@tap="clickItem(index)">
<view class="swiper-box" :style="{'backgroundColor':`${bgColorItem}`}">
<view class="swiper-content">
<slot :row='item' :index='index'>
<image v-if="item[fileType]==='image'" :src="item[imageKey]" mode='scaleToFill'>
<template v-else-if="item[fileType]==='video'">
<!-- :style="{'height':fullScreen ? '':'100%' }" 解决非全屏video显示问题 -->
<zVideo
:ignoreTip='false'
:ref='`video_${index}`'
:index='index'
:src="item[videoKey]"
:poster="item.poster"
:moveX='moveX'
:initial-time="item.currentTime || 0 "
@play="stopSwiperAuto"
></zVideo>
</template>
</slot>
</view>
</view>
</swiper-item>
</swiper>
<!-- indicatorDots 原生指示器开启时不显示下面自定义指示器
vertical为true 垂直方向 只写了支持右侧定位
-->
<div
v-if="!indicatorDots && list.length>1"
:class="['dot',vertical ? 'verticalDot':`${indicatorPos}`]"
>
<slot :list='list' :total='list.length' :activeIndex='activeCurrent' name="dot">
<!-- 指示器自定义-返回列表数据list 数组长度total 选中项索引activeIndex -->
<template v-if="mode==='number'">
<div>
{{activeCurrent+1}}/{{list.length}}
</div>
</template>
<template v-else>
<div
v-for="(item,index) in list"
:key="index"
@click="activeCurrent=index"
:style="{'background-color': activeCurrent==index ? indicatorActiveColor:indicatorColor}"
:class="['dotItem',`${mode}`, `${ activeCurrent==index ? 'defautActive':'' }` ]">
<span v-if="mode ==='index'">{{index+1}}</span>
</div>
</template>
</slot>
</div>
</view>
</template>
<script>
import zVideo from './video.vue'
export default {
name:'my-swiper',
components:{zVideo},
props: {
list:{//滑块视图容器数据
type:Array,
default:_=>[
{
type:'video',
poster:'https://img2.baidu.com/it/u=2141851239,1037607188&fm=26&fmt=auto&gp=0.jpg',
src:'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/360e4b20-4f4b-11eb-8a36-ebb87efcf8c0.mp4',
},
{type:'image', src:'https://img0.baidu.com/it/u=1570602913,157918019&fm=26&fmt=auto&gp=0.jpg'},
{type:'image', src:'https://img0.baidu.com/it/u=3464142916,229554071&fm=26&fmt=auto&gp=0.jpg'},
{
type:'video',
currentTime:120,//初始帧时间---默认缓存存储
poster:'https://img1.baidu.com/it/u=1297253752,1185196455&fm=26&fmt=auto&gp=0.jpg',
src:'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/a876efc0-4f35-11eb-97b7-0dc4655d6e68.mp4',
},
{type:'image', src:'https://img1.baidu.com/it/u=2057763469,3313822915&fm=26&fmt=auto&gp=0.jpg'},
{type:'image', src:'https://img0.baidu.com/it/u=1570602913,157918019&fm=26&fmt=auto&gp=0.jpg'},
]
},
fileType: { type: String, default: 'type' }, // 文件类型映射
videoKey:{ type:String, default:'src'},// 视频映射的键
imageKey:{ type:String, default:'src'},//图片映射的键
indicatorPos:{ type:String, default:'bottomCenter'},//指示器的位置:topLeft/topCenter/topRight/bottomLeft/bottomCenter/bottomRight
mode:{ type:String, default:'round' },//指示器样式: default circle round index number none时不显示
fullScreen:{ type:Boolean, default:false }, //是否全屏
navHeight:{type:Number, default:44},//顶部导航高度,默认44---垂直全屏状态无导航栏可设置为0
height:{ type:Number, default:160 },//swiper 高度单位px
contentRadius:{ type:String, default:'0rpx' },//盒子圆角设置
// topFloat:{ type:Boolean, default:true },//顶部不占位-浮动定位
// fotterFloat:{ type:Boolean, default:true },//底部不占位-浮动定位
bgColor:{type: String, default: '#f3f4f6'},//swiper背景色
bgColorItem:{type: String, default: 'rgba(0,0,0,0)'},//swiper当前项背景色
//顶部与底部设置-注:-顶部与底部根据需求自己拓展---也可用插槽自定义内容
topTextKey:{ type:String, default:'topTip'},//顶部文字说明映射的键
topColor:{ type:String, default:'#FFF'},//顶部文字颜色
topBackground:{ type:String, default:'rgba(0, 0, 0, 0)'},//顶部背景色
topTextAlign:{ type:String, default:'left'},//顶部文字位置
bottomTextKey:{ type:String, default:'bottomTip'},//底部文字说明映射的键
bottomColor:{ type:String, default:'#00F'},//底部文字颜色
bottomBackground:{ type:String, default:'rgba(0, 0, 0, 0)'},//底部背景色
bottomTextAlign:{ type:String, default:'left'},//底部文字位置
//---end
nextMargin:{ // 后边距,可用于露出后一项的一小部分,接受 px 和 rpx 值 头条小程序不支持
type:String,
default:'0rpx'
},
previousMargin:{//前边距,可用于露出前一项的一小部分,接受 px 和 rpx 值头条小程序不支持
type:String,
default:'0rpx'
},
vertical:{ type:Boolean, default:false },//滑动方向是否为纵向 卡牌不支持纵向以及同时显示的2块以上滑块数量
interval:{ type:Number, default:2500 },// 自动切换时间间隔
current:{ type:Number, default:0 },// 初始化时,默认显示第几项
autoplay:{ type:Boolean, default:true },// 是否自动切换
indicatorDots: { type: Boolean, default: false },//是否显示面板指示点--默认关闭使用自定义指示器mode设置指示器,原生指示器为true时 则不显示自定义指示器
indicatorColor:{ type:String, default:'rgba(0,0,0,0.3)' },// 指示点颜色
indicatorActiveColor: { type: String, default: '#F1F1F1' },// 选中项指示点颜色
},
data() {
return {
swiperList:[],//列表数据
videoContent:'',//视频实例
videoPlaySataus:false, //视频播放状态---默认禁用
activeCurrent:0,//当前选中索引
swiperHeight:0, //轮播图高度
moveX:0,
}
},
watch: {
height:{//swiper高度
handler(newValue) {
this.swiperHeight = newValue
},
immediate:true
},
current:{//初始化选中项
handler(newValue) {
this.activeCurrent = newValue
},
immediate:true
},
list:{//初始化数据列表--- 处理vue不能直接改变prpos属性
handler(newValue) {
this.swiperList = newValue || []
},
immediate:true
},
fullScreen:{
handler(newValue) {
if(this.fullScreen){//全屏设置---默认初始化设置一次
uni.getSystemInfo({
success:(e)=>{
console.log('e',e);
this.swiperHeight = e.screenHeight - this.navHeight
// #ifdef APP-PLUS || MP-WEIXIN
this.swiperHeight = e.screenHeight - this.navHeight - e.statusBarHeight
// #endif
}
})
}else{
this.swiperHeight = this.height
}
},
immediate:true
}
},
mounted(){
},
methods: {
// 播放视频时让轮播停止自动轮播
stopSwiperAuto(e){
console.log("111111111",e)
this.videoPlaySataus = e
},
timeupdate(e){//播放进度变化时触发--更新播放缓存
this.$set(this.swiperList[this.activeCurrent],'currentTime',e.detail.currentTime)
},
clickItem(index){
if(this.list.length>0){
this.$emit('clickItem',this.list[index])
}
},
change(e){//轮播改变触发
try{// 切换前暂停之前视频
let preSwiper = this.swiperList[this.activeCurrent]
if(preSwiper[this.fileType]==='video'){
// uni.createVideoContext(`video_${this.activeCurrent}`,this).pause();
this.$refs[`video_${this.activeCurrent}`][0].pausePlay()
}
}catch(e){
//TODO handle the exception
}
this.videoPlaySataus = false //自动切换关闭视频播放状态
this.activeCurrent = e.detail.current;
this.$emit('change',e)
},
animationfinish(e){//动画结束后调用
this.moveX = 0
this.$emit('animationfinish',e)
},
transition(e){//滑动
// #ifdef APP-PLUS
this.moveX = e.detail.dx
// #endif
},
touchStart(e){//触摸
console.log("e触摸: ",e);
},
touchEnd(e){//触摸
console.log("e触摸结束: ",e);
},
clickCover(e){
console.log("点击: ",e);
},
touchmove(e){
console.log("滑动中: ",e);
}
}
}
</script>
<style lang="scss" scoped>
.swiperContent{//容器
width:100%;
position: relative;
// background-color: #ccc;
.z-screen-swiper {//轮播图
min-height: 320rpx;
// background-color: rgb(211, 235, 107); //--调试样式
box-sizing: border-box;
.z-swiper-item{
box-sizing: border-box;
overflow: initial;
.swiper-box{//轮播图内容
// background-color: #e7ca8f;//--调试样式
// background-color: rgba(0,0,0,0.1);//swiper当前项 背景色---已改为配置
box-sizing: border-box;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
color: #FFF;
.swiper-top{
top: 0;
// background-color: rgba(0,0,0,0.2);//背景色---已改为配置
}
.swiper-content{
// background-color: rgb(61, 41, 175);
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
image{
width:100%;
height: 100%;
max-height: 100%;
}
video {// 视频默认不全屏高度---防止全屏swiper滑动切换
width: 100%;
height: 100%;
// height: 750rpx;
// pointer-events: auto;
}
}
.swiper-fotter{
bottom: 0;
// background-color: rgba(0, 0, 0, 0.2); //背景色---已改为配置
}
.isFloat{//是否浮动 顶部、底部定位
position: absolute;
left: 0;
right: 0;
z-index: 999;
}
}
}
}
// .effect3D{//3d模式样式
// .z-swiper-item{//3d模式基础样式
// .swiper-box{
// border-radius: 10rpx;
// opacity: 0.7;
// transition: all 0.1s ease-in 0s;
// }
// }
// &.effect3D-X{
// .z-swiper-item{ //选项卡间隔
// padding: 0 10rpx;
// }
// .swiper-box{
// transform: scale(1,0.9);
// }
// }
// &.effect3D-Y{
// .z-swiper-item{ //选项卡间隔
// padding:10rpx 0;
// }
// .swiper-box{
// transform: scale(0.9,1);
// }
// }
// .active-swiper{//选中样式恢复
// .swiper-box{
// transform:initial;
// opacity: 1;
// transition: all 0.1s ease-in 0s;
// }
// }
// }
.dot{//指示器
position: absolute;
z-index: 9999;
display: flex;
color: #FFF;
.dotItem{//指示器 颜色与形状
background-color: #fff;
font-size: 24rpx;
color: #e2e2e2;
margin-right: 10rpx;
&.default{ /*默认条状 */
height: 8rpx;
width: 40rpx;
}
&.circle{ /* 圆 */
height: 20rpx;
width: 20rpx;
border-radius: 50%;
}
&.index{ /* 数字索引 */
height: 30rpx;
width: 30rpx;
display: flex;
justify-content: center;
align-content: center;
border-radius: 50%;
}
&.defautActive{//选中项设置
transition: background-color 0.3s ease-out 0s; //选中动画
}
&.round{ /* 弧形 */
height: 20rpx;
width: 20rpx;
border-radius: 50%;
&.defautActive{//弧形选中项设置
width: 60rpx;
height: 20rpx;
border-radius: 10px;
transition: background-color 0.3s ease-out 0s; //
}
}
}
// 定位位置
&.verticalDot{//垂直方向 只写了支持右侧定位
right: 20rpx;
top: 50%;
transform: translateY(-50%);
display: block;
margin: 0 !important;
.dotItem{
margin: 0;
margin-bottom: 10rpx;
&.round{ /* 弧形 */
height: 20rpx;
width: 20rpx;
border-radius: 50%;
&.defautActive{//弧形选中
width: 20rpx;
height: 60rpx;
border-radius: 10px;
}
}
}
}
&.bottomLeft{//左上角
left: 20rpx;
bottom: 20rpx;
}
&.bottomCenter{//
left: 50%;
bottom: 20rpx;
transform: translateX(-50%);
}
&.bottomRight{
right: 20rpx;
bottom: 20rpx;
}
&.topLeft{
left: 20rpx;
top: 10rpx;
}
&.topCenter{
left: 50%;
top: 10rpx;
transform: translateX(-50%);
}
&.topRight{
right: 20rpx;
top: 10rpx;
}
}
}
</style>
<style lang="scss" scoped>
#coverViewVideo{
height: 200rpx;
background-color: #ccc;
width: 100%;
}
</style>
components/my-swiper/video
uni.createVideoContext(id,this); //this一定要加,否则在微信小程序端有问题
<template>
<!-- 视频单项 -->
<view class="video-layout">
<video
v-show="isplay"
:id="`myVideo${index}`"
:style="{ height: height, width: '100%' }"
:src="src"
controls
objectFit="contain"
:enable-progress-gesture="enableProgressGesture"
x5-video-player-type="h5"
x-webkit-airplay="allow"
webkit-playsinline="true"
@error="videoErrorCallback"
@play="play"
@pause="pause"
>
<!-- #ifdef APP-PLUS -->
<cover-view :style="{ transform: 'translateX(' + moveX + 'px)' }" />
<!-- #endif -->
<!-- 不使用弹窗提示,视频内部提示可使用cover-view自定义播放提示样式 -->
</video>
<view v-show="!isplay" class="image" :style="{ height: height, width: '100%' }" @click="beforePlay">
<image mode="aspectFill" :src="poster" />
<view class="play" />
</view>
</view>
</template>
<script>
// enable-progress-gesture 手势滑动在非app页面开启后,视频轮播会存在与轮播图,滑动事件同时触发的情况
export default {
props: {
moveX: { type: [Number, String], default: 0 }, // 轮播图兼容滑动兼容-单独使用可不传
index: { type: [Number, String], default: 0 }, // 下标索引
height: { type: String, default: '750rpx' }, // 视频高度
borderRadius: { type: Number, default: 0 }, // 圆角值,单位rpx
videoSize: { type: [Number, String], default: 10 }, // 视频大小
ignoreTip: { type: Boolean, default: true }, // 播放环境提示
// #ifdef APP-PLUS
enableProgressGesture: { type: Boolean, default: true }, // 手势滑动
// #endif
// #ifndef APP-PLUS
enableProgressGesture: { type: Boolean, default: false }, // 手势滑动
// #endif
src: {
// 播放地址
type: String,
default: 'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/360e4b20-4f4b-11eb-8a36-ebb87efcf8c0.mp4'
},
poster: {
// 封面
type: String,
default: 'https://img1.baidu.com/it/u=1601695551,235775011&fm=26&fmt=auto'
}
},
data() {
return {
// videoContext: null, // 视频实例
isplay: false, // 播放状态
isTip: true // 是否提示
};
},
watch: {
ignoreTip: {
handler(v) {
this.isTip = v;
},
immediate: true
}
},
onReady() {
// this.videoContext = uni.createVideoContext('myVideo')
},
methods: {
videoErrorCallback: function(e) {
console.log('视频错误信息:');
console.log(e.target.errMsg);
},
play() {
console.log('播放');
this.$emit('play', true);
},
pause() {
console.log('暂停');
this.isplay = false;
this.$emit('play', false);
},
startPlay() {
// 开始播放
this.isplay = true;
this.$nextTick(() => {
const id = `myVideo${this.index}`;
//一定要加this,不然微信小程序有问题(需要需要触发两次才能play)
const video = uni.createVideoContext(id,this);
video.play();
});
},
pausePlay() {
//暂停播放
const id = `myVideo${this.index}`;
//一定要加this
const video = uni.createVideoContext(id,this);
video.pause();
this.isplay = false;
},
beforePlay() {
// 播放前拦截
this.isplay = true; // 拦截前显示播放视频
if (!this.isTip) return this.startPlay(); // 不提示直接播放
// https://uniapp.dcloud.io/api/system/network?id=getnetworktype
uni.getNetworkType({
success: res => {
const networkType = res.networkType;
if (networkType === 'wifi' || networkType === 'ethernet') {
this.startPlay();
} else {
uni.showModal({
title: '提示',
content: `当前为移动网络,播放视频需消耗${this.videoSize}M流量,是否继续播放?`,
success: res => {
if (res.confirm) {
this.startPlay();
this.isTip = false;
} else {
this.isplay = false;
}
}
});
}
}
});
}
}
};
</script>
<style lang="scss" scoped>
.video-layout {
display: flex;
align-items: center;
// video{
// width: 100%;
// height: 100%;
// /deep/.uni-video-container{
// width: auto;
// height: auto;
// }
// }
.image {
position: relative;
width: 100%;
height: 100%;
/deep/uni-image {
width: 100%;
height: 100%;
}
.play {
position: absolute;
left: 50%;
top: 50%;
width: 80rpx;
height: 80rpx;
transform: translate(-50%, -50%);
// background-image: url('@/static/play.png');
background-image: url('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_pic%2F20%2F07%2F01%2F215e4e632b7438794f5b75fe8ad35388.jpg%21%2Ffw%2F253%2Fquality%2F90%2Funsharp%2Ftrue%2Fcompress%2Ftrue&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1661933376&t=47385c16830ca399e0873f50474fbeeb');
background-size: contain;
background-repeat: no-repeat;
background-color: rgba($color: #000000, $alpha: 0.1);
border-radius: 50%;
}
}
}
</style>