uniapp 微信小程序 多个视频 播放优化问题

2,836 阅读2分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。

最近 接到领导安排的一个任务,是关于微信小程序的,有一个文章列表,类型 有 图文和视频,并且 视频在列表中可以直接播放,现有功能已经写好了,现在要处理一下优化问题,就是播放中的视频只能存在一个,也就是在点击播放一个视频的时候,其他播放中的视频要暂停。

想了下 蛮简单,利用 Api 的 createVideoContext和pause() 就能解决了

按说应该开发这个功能的人分分钟不就加上了,当前页面 循环判断不就行了

事出反常必有妖 ,当我看了下代码的时候,就明白了,果然 没我想象的那么简单,组件嵌套了 多层

  • pageA(主页)中引入了 组件A(列表) 传进(props)列表数据 list

  • 组件A中 又引入 组件B 根据list循环 组件B 传入 详情 detail数据

  • 在组件B图文和视频 接收 detail 进行渲染

在不动原有代码的基础上优化起来就有一定难度,虽然麻烦,但也是可以处理的,通过父子孙之间的组件通信就能解决, 但随后又考虑到,这个视频优化需求,还涉及到详情页,和部分活动页面,等等等等, 要是挨个写可就没完没了了,于是,想把视频 播放这个功能 单独封成一个组件videoHandle,只需要把所有video标签替换成videoHandle就可以了,一劳永逸。

接下来就是想办法处理所有 videoHandle组件之间的通信了,最终采用了 发布订阅模式来解决

项目是基于 uniapp 的,页面文件遵循 Vue 单文件组件 (SFC) 规范

手写 发布订阅

创建 myevent.js

export default class myevent{
    // 存储事件队列
    constructor(){
    
      this.msg = {}
      
    }
    

   /**
    * subscribe 订阅事件
    * @param {*} key 事件标识符
    * @param {*} fn  事件方法
    */
    subscribe(key, fn){
        ...
    }
    
   /**
    * 发布队列中所有事件,通知所有订阅者
    */
    publish() {
       ...
    }
   /**
    * 事件删除,传入key值(fn选填)
    * @param {*} key 
    * @param {*} fn 
   */
    remove(key, fn){
      ...
    }
    
   /**
    * 获取当前 队列数
    */
    getQueue(key){
        ...
    }
}

订阅事件

subscribe(key, fn){

    // 如果回调函数不是个方法 直接返回
    if(typeof fn != 'function') return
    
    // 如果此队列不存在,创建个新的
    if(!this.msg[key]) this.msg[key] = []
    
    // 添加 事件的到消息队列中去
    this.msg[key].push(fn)
}

发布通知订阅者

为了方便传入接收多个参数 使用了 arguments

arguments

publish() {
   
    // 获取参数key ,并删除第一个参数
    let key = Array.prototype.shift.call(arguments)
    
    let fns = this.msg[key]
    
    //  事件为空就返回
    if(!fns||!fns.length) return
    
    fns.forEach(res => {
        // 执行事件,并传入参数
        res.apply(null, arguments)
        
    })
}

清除事件

remove(key, fn) {
    let fns = this.msg[key]
    
    // 如果不存在 返回
    if(!fns || !fns.length) return
    
    // 如果不传fn 则删除这个队列
    if(!fn) {
     delete this.msg[key] 
    }else {
        // 循环 删除指定的 事件
        for(let i=0;i<fns.length;i++){
              let item = fns[i]
              if(item === fn || item.fn === fn) {
                  fns.splice(i,1) 
                  break
              }
        }
    }
}

获取当前 队列数

为了知道队列中事件 数量,可以调用此方法查看

getQueue(key){
    if(!this.msg[key]) return undefined
    return this.msg[key].length
}

在main.js引入 myevent

...

import myevent from '@/common/utils/myevent'
Vue.prototype.$event = new myevent()

...

视频组件 videoHandle

在创建组件的时候,生成唯一guid,订阅eventSuspend消息,并绑定 接收消息事件 receiveSuspend,当播放视频的时候,携带此guid 发布消息,通知所有订阅者(所有的videoHandle,包括此组件),接收到通知后,通过guid 来处理当前视频的播放暂停动作

具体代码如下

<template>
    <video 
     class="my-video"
     :id='id'
     :src="url"
     object-fit="cover"
     @error="videoErrorCallback"
     @play="playing"
     controls
    >
    </video>
</template>
<script>

export default {
  props: {
    url: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      id:''
    }
  },
  created() {
    this.id = this.$u.guid()
    // 订阅 eventSuspend 消息 并绑定  this.receiveSuspend()
    this.$event.subscribe('eventSuspend', this.receiveSuspend)
  },
  methods: {
    videoErrorCallback (e) {
      console.log('videoErrorCallback')
      console.log(e)
    },
    // 接收消息通知
    receiveSuspend(id){
      console.log('暂停播放suspend', this.id, id)
      
      // 接收的 id与本视频id不符 则停止播放
      if(id && this.id && id !== this.id){
       this.pause()
      }
    },
    // 播放时触发
    playing(){
      // 发布消息,通知所有订阅者
      this.$event.publish('eventSuspend', this.id)
    },
    // 暂停视频播放
    pause(){
      wx.createVideoContext(this.id, this).pause()
    }
  },
  // 退出页面时 取消订阅
  destroyed () {
    this.$event.remove('eventSuspend', this.eventSuspend)
  },
};
</script>
<style scoped lang="scss">

.my-video{
    width: 100%;
    height: 100%;
}
</style>