uniapp APP端视频轮播问题

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>

猜你喜欢

转载自blog.csdn.net/weixin_45028704/article/details/126104092
今日推荐