uniapp 上下切换视频,优化版

借鉴了uniapp插件市场(https://ext.dcloud.net.cn/plugin?id=5656)防抖音模块的写法,自己重构了下,更贴合自己项目,

平台兼容性:app-nvue

videoPlayer.nvue

<template>
    <view class="videoPlayer">

        <video v-if="showVideo" id="myVideo" class="video" :src="src" ref="myVideo" :muted="isMuted"
            object-fit="contain" :loop="true" :autoplay="false" :show-play-btn="false" :enable-progress-gesture="false"
            :page-gesture="false" :controls="false" :http-cache="true" :show-loading="false"
            :show-fullscreen-btn="false" :show-center-play-btn="false" :style="boxStyle"
            @timeupdate="timeupdate($event)"></video>
        <image class="snapshot" :class="!playing?'':'snapshot_hide'" :src="src+snapshot" mode="aspectFit"
            :style="boxStyle">
        </image>
        <view class="videoHover" :style="boxStyle" @click="click">
            <image v-if="state=='pause'" class="playState" src="@/static/video/play.png"></image>
        </view>

    </view> 
</template>

<script>
    /* 
      * @property  src 视频地址
      * @property  state 视频状态(play:播放,continue:暂停后继续播放,pause:暂停 ,stop:暂停并归零,preplay:预加载)
      * @property  index 列表下标(用于进度条判断是否为当前视频)
      * @property  boxStyle 视频宽高(取值与列表页面的宽高)
      * @property  showVideo 是否显示视频(列表Math.abs(currentIndex-index)<=1的boolen值,避免视频元素过多占内存)
      * @property  seektime 跳转进度
     */
    var timer = null

    export default {
        props: ['src', 'state', 'index', 'boxStyle', 'showVideo', 'seektime'],
        watch: {
            state: {
                immediate: true,
                handler() {
                    this.stateChange();
                }
            },
            seektime: {
                immediate: true,
                handler() {
                    let videos = this.getVideoContext(); 
                    if (!!videos) {
                        let videoContext = videos;
                        this.isMuted = false
                        this.playing = true
                        if (this.seektime > 0) {
                            videoContext.seek(this.seektime);
                            videoContext.play()
                        }
                    }
                }
            }
        },
        data() {
            return {
                snapshot: '?x-oss-process=video/snapshot,t_0000,f_jpg', //阿里云OSS媒体视频截帧 https://help.aliyun.com/document_detail/64555.html
                //snapshot:'?ci-process=snapshot&time=1',//腾讯云COS媒体视频截帧 https://cloud.tencent.com/document/product/436/55671
                playing: false, //是否播放中,控制图片(视频首帧)显隐
                isMuted: false, //是否静音
                dblClick: false,
                initplay: true,
            };
        },
        mounted() {

        },
        methods: {
            getVideoContext(){
                //也可以使用 uni.createVideoContext('myVideo',this)
                return this.$refs['myVideo'];
            },
            timeupdate(e) {
                if (this.initplay && e.detail.currentTime > 0) { //大于0说明视频有图像了,可以隐藏首帧图片了,并将进度重新归为0
                    this.initplay = false;
                    let videos = this.getVideoContext(); 
                    if (!!videos) {
                        let videoContext = videos;
                        videoContext.seek(0); //因为视频首帧用的1s,所以跳到这里页面可以无缝切换

                        videoContext.play()
                        this.playing = true
                        this.isMuted = false
                    }
                } else {
                    //通知进度条进行更新
                    this.$emit('timeupdate', e, this.index);
                } 

            },
            async stateChange() {
                let videos = this.getVideoContext(); 
                if (!!videos) {
                    let videoContext = videos;
                    switch (this.state) {
                        case 'play': //视频播放
                            if (!this.playing) {
                                //timeupdate方法在uniapp这个版本的bug,只有暂停再播放,timeupdate的值才能刷新
                                videoContext.pause();
                                this.initplay = true
                                this.isMuted = true
                                this.playing = false
                                videoContext.play()
                            }

                            break;
                        case 'continue': //暂停后的播放,不需要用图片盖住
                            this.isMuted = false
                            this.playing = true
                            videoContext.play()
                            break;
                        case 'pause': //视频暂停 
                            videoContext.pause()
                            this.playing = true //暂停后的播放,不需要用图片盖住
                            break;
                        case 'stop': //上下切换停止视频

                            videoContext.pause(); //视频停止,从0开始
                            videoContext.seek(0);
                            this.playing = false
                            break;
                        case 'preplay': //预加载视频,让视频静音播放1.5秒时间
                            this.isMuted = true
                            this.playing = false
                            videoContext.play();
                            await setTimeout(() => {
                                videoContext.seek(0);
                                videoContext.pause(); //视频停止,从0开始 
                                this.playing = false
                            }, 1500)
                            break;

                        default:
                            break;
                    }
                }

            },
            click() {
                clearTimeout(timer)
                this.dblClick = !this.dblClick
                timer = setTimeout(() => {
                    if (this.dblClick) { //判断是单击 即为true
                        //单击
                        if (this.state != "continue" && this.state != "play") {
                            this.state = "continue"
                        } else {
                            this.state = "pause"

                        }

                    } else {
                        //双击
                        this.$emit('dblClick') //向父组件传递一个事件
                    }
                    this.dblClick = false //点击后重置状态 重置为false
                }, 300)
            },
        }
    }
</script>

<style>
    .videoPlayer {
        flex: 1;
        background-color: #000000;
    }

    .video {
        flex: 1;
    }

    .snapshot {
        position: absolute;
    }

    .snapshot_hide {
        opacity: 0;
    }

    .videoHover {
        position: absolute;
        top: 0;
        left: 0;
        flex: 1;
        background-color: rgba(0, 0, 0, 0.1);
        justify-content: center;
        align-items: center;
    }

    .playState {

        width: 160rpx;
        height: 160rpx;
        opacity: 0.2;

    }
</style>

subVideoList.nvue

<template>
    <view class="page" :style="boxStyle">
<!--先屏蔽,便于修改bug: 推荐 朋友 关注 搜索 发布小视频(+)-->
 <!-- <video-tabBar ></video-tabBar> -->
        <list :loadmoreoffset="loadmoreoffset" :show-scrollbar="false" :pagingEnabled="true" :scrollable="scrollable"
            @scrollstart="onScrollStart" @scrollend="onScrollEnd" @scroll="onScroll" @loadmore="loadMore">
            <cell class="itemBox" v-for="(item, index) in videos" :key="index" :style="boxStyle">
                <!-- 动态渲染视频为3个-->
                <video-player :state="item.state" :index="index" :seektime="item.seektime"
                    :showVideo="Math.abs(currentIndex-index)<=1" :src="item.src" :boxStyle="boxStyle"
                    @dblClick='dblClick' @timeupdate='timeupdate'> </video-player>
             <!--先屏蔽,便于修改bug: 头像,关注,点赞,收藏,分享,商品链接-->
                <!-- <video-info class="infoBox" :item="item"></video-info> -->


            </cell>
        </list>
        <!-- 拖动进度时,显示拖动时间-->
        <view v-if="videoProgressDarg" class="progressBar_drag_time" >
            <text style="font-size: 22px; font-weight: bold; color: #F1F1F1;">{
    
    {dragTimeStr}} / {
    
    {durationStr}}</text>
        </view>
        <!-- 可拖动进度条,触摸高度80upx -->
        <view v-if="videoProgressBar" class="progressBar" @touchstart="touchstart" @touchmove="touchmove"
            @touchend="touchend">
            <view class="progressBar_ground"></view> 
            <view v-if="!videoProgressDarg" class="progressBar_value" :style="{'width':playProgress+'px'}"></view>
            <view v-if="!videoProgressDarg" class="progressBar_slider" :style="{'left':playProgress+'px','width':'10upx'}"></view>
            <view v-if="videoProgressDarg" class="progressBar_drag_value" :style="{'width':dragProgress+'px'}"></view>
            <view v-if="videoProgressDarg" class="progressBar_drag_slider" :style="{'left':dragProgress+'px','width':'10upx'}"></view>
        </view>
    </view>
</template>

<script>
    var deviceInfo = uni.getSystemInfoSync();
    let scrollTimer = null;
    let preplayTimer = null;
    import videoPlayer from './videoPlayer.nvue'
 
    export default {
        components: {
            videoPlayer,
      
        },
        data() {
            return {
                loadmoreoffset: deviceInfo.windowHeight * 2, // 触发 loadmore 事件所需要的垂直偏移距离 加载到剩余2个的时候 继续加载
                boxStyle: {
                    width: deviceInfo.screenWidth + 'px',
                    height: (deviceInfo.windowHeight - 32) + 'px'
                },
                screenWidth: deviceInfo.screenWidth,
                wHeight: deviceInfo.windowHeight - 32, //32是指底部tabBar高度,根据自己情况修改
                videos: [],
                currentIndex: 0,
                scrollable: true,
                videoProgressBar: true, //滑动过程中隐藏,同时避免进度条多次触发
                videoProgressDarg: false, //是否拖拽进度条
                playProgress: 0, //播放进度
                dragProgress: 0, //拖拽进度
                duration: 0, //视频总时长
                dragTime: 0,
                dragTimeStr: '00:00', //拖拽时间点
                durationStr: '00:00', //视频总时长
            };
        },
        onLoad() {

        },
        onReady() {
            this.init();

        },
        watch: {
            async currentIndex(newIndex, oldIndex) {
                await this.setState(oldIndex, 'stop');
                await this.setState(newIndex, 'play');

                if (preplayTimer) {
                    clearTimeout(preplayTimer);
                    preplayTimer = false;
                }
                preplayTimer = setTimeout(() => {
                    this.setState(newIndex + 1, 'preplay'); //预加载下一个视频  
                }, 500)

            }
        },
        methods: {
            //初始化
            async init() {
                await this.getList(); //初次加载需要等待数据请求完毕
                this.currentIndex = 0;
                this.$nextTick(() => {
                    this.setState(0, 'play');
                })
            },
            getList() {
                return new Promise((resolve, reject) => {
                    this.videos = [{ //1
            
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/710b0cf7-bed9-4805-a2fb-0b703483dbec.MOV", //10.视频链接
                    }, { //2

                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/ec383f81-6896-4274-8861-e329ae1376b4.mp4",
                    }, { //3

                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/53543262-55f5-4685-a5e3-b56ce75bcb88.mp4",
                    }, { //4
              
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/bfc86ab8-bb3b-4cef-a5d2-8c5edce4ef17.mp4",
                    }, { //5
                     
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/5017a17a-389b-45e0-8d91-711c9dc76759.mp4",
                    }, { //6
                      
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/209180d8-3dfd-42ea-9ef5-5f98ae0d95e1.mp4",
                    }, { //7
       
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/c8f7a17f-6eb8-453a-9f43-944ecc7a9f11.mp4",
                    }, { //8
                
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/f905c750-c70e-46b2-aaa6-37778d308f13.mp4",
                    }, { //9
                       
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/9392e85c-36db-473f-8ec3-4f8ed83a382a.mp4",
                    }, { //10
                      
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-0455454d-b373-4768-aa39-dc1226fc1362/e1cd785e-56ae-4c96-a713-126bf2950e19.mp4",
                    }, {
                        //11
                       
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/97b50a6d-f77d-4418-b38d-844e0b9eec97.mp4",
                    }, {
                        //12
                    
                        "state": "pause",
                        "src": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-2908110e-6da2-4899-8b44-d45c153457ad/c061b07a-4b34-4d6d-aa1a-2cf41679f17c.mp4",
                    }]

                    resolve();
                });
            },
            dblClick() {

            },
            loadMore(e) {
                console.log("加载更多");
            },
            touchstart() {
                this.scrollable = false; //禁止滑动
                this.dragProgress = this.playProgress;
                this.videoProgressDarg = true;
                let dragTime = this.duration * (this.playProgress / this.screenWidth);
                this.dragTime = dragTime;
                this.dragTimeStr = this.getTime(dragTime); //视频当前播放时长
                this.durationStr = this.getTime(this.duration);
            },
            touchmove(event) {
                let left = Math.round(event.changedTouches[0].screenX);
                let dragTime = this.duration * (left / this.screenWidth);
                this.dragProgress = left;
                this.dragTime = dragTime;
                this.dragTimeStr = this.getTime(dragTime);
            },
            touchend() {
                let index = this.currentIndex;  
                if (0 <= index && index < this.videos.length) {
                    this.$set(this.videos[index], 'seektime', this.dragTime);
                }  
                this.scrollable = true;
                this.videoProgressDarg = false; 
            },
            timeupdate(event, index) {
                if (!this.videoProgressDarg&&this.videoProgressBar && this.currentIndex == index) { //不加判断会导致预播放视频也触发,导致进度条跳动;同时滑动的时候也不处理
                    let duration = event.detail.duration;
                    let currentTime = event.detail.currentTime; 
                    if (this.playProgress <1) { //总时间赋值一次即可 
                        this.duration = duration;
                    }
                    this.playProgress = this.screenWidth * (currentTime / duration);
                }

            },
            getTime(time) {
                /*                 let h = parseInt(time / 60 / 60 % 24)
                                h = h < 10 ? '0' + h : h */
                let m = parseInt(time / 60 % 60)
                m = m < 10 ? '0' + m : m
                let s = parseInt(time % 60)
                s = s < 10 ? '0' + s : s
                return m + ":" + s;
            },
            onScrollStart() {
                this.videoProgressBar = false;
            },
            onScrollEnd() {
                this.playProgress = 0; //播放进度归零
                this.duration = 0; //视频总时长
                this.videoProgressBar = true;
            },
            onScroll(event) {
                if (!event.isDragging) { //是否拖拽滑动
                    var index = Math.round(Math.abs(event.contentOffset.y) / this.wHeight) //获取视频下标
                    if (index !== this.currentIndex) {
                        clearTimeout(scrollTimer);
                        scrollTimer = setTimeout(() => {
                            this.currentIndex = index //当前下标 
                        }, 100)
                    }
                }
            },
            setState(index, state) {
                if (0 <= index && index < this.videos.length) {
                    this.$set(this.videos[index], 'state', state)
                }
            },

        }
    }
</script>

<style>
    .page {
        flex: 1;
        background-color: #000000;
    }

    .itemBox {
        position: relative;

    }

    .progressBar {
        position: absolute;
        right: 0;
        left: 0;
        bottom: 0;
        height: 80upx;
    }

    .progressBar_ground {
        position: absolute;
        right: 0;
        left: 0;
        bottom: 20upx;
        height: 4upx;
        background-color: #b2b1b4;
    }

    .progressBar_value {
        position: absolute;
        left: 0;
        bottom: 20upx;
        height: 4upx;
        background-color: #ffffff;
    }

    .progressBar_slider {
        width: 10upx;
        height: 10upx;
        background-color: #ffffff;
        border-radius: 2px;
        position: absolute; 
        bottom: 17upx; 
    }

    .progressBar_drag_value {
        position: absolute;
        left: 0;
        bottom: 20upx;
        height: 10upx; 
        background-color: #ffffff;
    }

    .progressBar_drag_slider {
        width: 10upx; 
        height: 20upx;
        background-color: #ffffff;   
        border-radius: 2px;
        position: absolute;
        bottom: 14upx;
    }

    .progressBar_drag_time {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 60upx; 
        align-items: center; 
    }

    .infoBox {
        position: fixed;
        right: 0;
        bottom: 0;
    }
</style>

videoPlayer.nvue管理视频,使用页面如 短视频列表,视频+图片混合预览,论坛视频,商品视频,轮播图视频,拍摄视频预览等,如果是vue页面使用,请使用subNVues

猜你喜欢

转载自blog.csdn.net/csdn_zuirenxiao/article/details/128646460