autojs遍历当前页面所有控件_vue 如何抽象一个客户端级的 audio 模块(多页面同时控制)...

本文转载于SegmentFault社区

作者:绒球帽


因为很喜欢看vtuber哈哈,最近进了vtuber-music的项目组参与开发,项目是vue项目,但因为UI的设计很偏向客户端,我设计了这个客户端级的audio模块,可多个页面同时控制播放,暂停,切歌,改变音量,播放位置等操作

项目公告板:https://blog.vtbmusic.love
项目开发环境快照:http://47.103.218.183
项目开源地址:https://github.com/vtbmusic/VtuberMusic  (dev分支是最新开发进度,其他是灾备应急备份及版本管理)

逻辑原理


先介绍下逻辑原理,由于需要可支持需要多个页面同时控制(在别的页面也有诸如播放暂停,切歌,替换歌单的需求),这个audio模块设计为单例化audio实例,我的逻辑思路是:设计一个专注于一个audio标签的组件,加载到app.vue下随项目启动唤醒。数据依靠监听vuex这类状态管理工具(当前用的就是vuex),并抽象出一个单独的js文件用于控制vuex(实际上ts更规范,这里我用js完全是懒了),再把这个文件注册到vue全局,这样在整个项目的任何一个地方都可以非常方便的控制这个audio的状态,例如:

this.$audioSet.Play()         _// 在任何一个页面的任何一段代码中使用这条命令就可以控制audio模块进行播放_

关系示意图


21aa2db4d7b6231cb1834717a1c05fd9.png

实现方法


从头开始说,App.vue下引入一个vue组件,并只用放一个audio标签就行,App.vue的代码:

<template>  <div id="app" class="app_setting">    <router-view />        <AudioModel />  div>template><script>  import AudioModel from './components/audio/index.vue'  export default {    name: 'App',    components: {      AudioModel    },...

这个方法十分简单易懂,这个audio组件内主要负责:从vuex绑定数据依赖,并watch数据变化以改变audio的方法来控制audio,我以 播放暂停 功能举例,AudioModel组件的代码:

<template>  <div>    <audio ref="audio" @timeupdate="onUpdateTime" :autoplay="false" />  div>template><script>  import {mapState} from "vuex";  export default {    name: 'AudioModel',    computed: {      ...mapState({        // vuex中有播放暂停状态,名字就是pauseStatus        pauseStatus: state => state.song.pauseStatus,      })    },    watch: {      pauseStatus(state) { // 检测到播放暂停状态改变        this.handlePlay(state)      },    },    methods: {      /** 播放暂停控制 */      handlePlay(state) {        !state ? this.audioPlayFade() : this.audioPauseFade();      },      /** 当audio标签更新进度时间是触发,绑定数据到本组件 */      onUpdateTime(e) {        // 在这里把进度信息提交给vuex        console.log(e.target)      },    }  }script>

你可能注意到我用到了vuex中的pauseStatus参数,由于我只是以 播放暂停 功能为例,实际上需要满足的所有功能涉及的参数众多,我只粘贴了播放暂停涉及到的部分,下面是store部分代码:

export default {  state: {            /** 播放整体 部分 */    pauseStatus: true,   // 当前播放是否是暂停状态,true为暂停  },  getters: {    getPause: state => { // 暂停状态      return state.pauseStatus    },  },  mutations: {    setPause(state){ // 设置为暂停      state.pauseStatus = true    }  }}

到目前位置所有的状态管理工作就完成了,但是怎么控制这个audio播放呢,我另单独写了一个audio.js文件并注册到全局(记得在main.js里注册,我就不单独贴在下面了),以下是audio.js文件部分代码:

import Vue from 'vue'import store from './../store'/** * 播放控制模组 * */const Play = () => store.commit('setPlay')const Pause = () => store.commit('setPause') /** 调用方式 - * [例] this.$audioSet.Play() */export default function(Vue) {    // 记得在main.js注册到全局    Vue.prototype.$audioSet = {        Play,         // 播放        Pause,        // 暂停    }}

当你完成了以上的操作,就可以在这个项目的任何地方使用  this.$audioSet.Play()  来控制这个播放器模块啦,

9ec45f024a58209a02627dbec6803aff.png

4ac3cd00d5781c1edb821fa580e5e31c.png

视图绑定


以上操作我成功抽象了audio模块的逻辑部分,但是如何与上图的页面视图绑定在一起呢,如何高效的利用这个绑定呢,你可能注意到了文章前面手绘的 关系示意图 里视图部分和js部分没有画在一起,并且是刻意是分开的,这实际上偏向于规范化格式而非技术难点,并且达到了两个目的

【1】将视图层和逻辑层解耦,即使是遇到较差的网络环境或未知bug的情况,由于视图直接反馈的是audio的当前状态信息,并不会与用户的主观感知做出的操作冲突(比如避免了由于网络环境不好暂停和播放可能由于更新不及时而与实际显示按钮状态相反的bug)

【2】将视图层和逻辑层解耦,极大的方便了后续页面的功能拓展(由于逻辑层和视图层没有直接关联,在编写逻辑代码时并不需要考虑视图层的状态情况,提高了代码可读性。且在后续开发中,即便有新的需求,也无需处理页面的交互情况,只管开发逻辑即可)

这里我同样以 播放暂停 按键继续举例,下面是部分视图层代码:

<template>    <div @click="handlePlay">    <i class="icon iconfont icon-play mid-pause" v-if="pauseStatus" />    <i class="icon iconfont icon-pause" v-else />  div>template><script>  import { mapState } from 'vuex'  export default {    computed: {      ...mapState({        pauseStatus: state => state.song.pauseStatus // 全局暂停状态      }    },    // 注意这里不应watch暂停状态来改变视图,会有严重的性能问题,后文我会写到    methods: {      /** 暂停 播放 */      handlePlay() {        // 这里我在改变状态时输出给封装的audio组件的命令与视图相关        // 而视图的改变又不依赖该部分逻辑,保证了用户的感知与指令的一致性        this.pauseStatus ? this.$audioSet.Play() : this.$audioSet.Pause()      },    }  }script>

到了这一步已经完成了一个简单的抽象audio全局控件,但是在你完善这个控件的时候会逐渐发现些问题

性能瓶颈


如果你细心的化一定会发现在上文 AudioModel组件 的部分示例代码中用了 onUpdateTime 这个方法将实时播放进度提交给vuex,并且由视图层中的进度条去绑定这一数据。那么问题来了,如果你翻阅过w3c的文档或者实际测试过audio这个标签,会发现 timeupdate 这个钩子触发频率是一秒钟四次,那么就意味着在audio这边每秒钟要向vuex写入4次,而视图层这边每秒钟需要监听到四次,再加上滚动歌词也需要监听这一数据,这就极大的增高了系统开销,再加上如果你对keepalive运用的不是很熟练的话(在vtuber-music这个项目中开始就是XD)甚至有可能使非常低配置设备的浏览器直接宕掉,例如我的19年的mba用的是8系i5的处理器,已经算是用户中等水平了,用谷歌浏览器跑仍然最高推到了80%cpu占比(后来通过优化降到了14%)

处理这个性能瓶颈的问题有很多很多条路,我只提及其中尝试过还不错的一种方案

在视图层这里一定要慎用keepalive,即使用了也要保证在每时每刻最多仅有一个正在显示中的组件watch这个进度数据(不要让不显示的组件在后台增大开销)

在audio这边的逻辑处理上用throttle降频,比如每秒钟提交一次数据就挺好的,进度数据更新频率横向对比:

网易云web端是1秒1次

QQ音乐web端甚至是3秒一次(?摸爆)

哔哩哔哩web端是1秒2次

结尾


至此,一个简单的audio抽象组件就完成啦,本篇讲解只是以 播放暂停 功能为例,实际上真正实现一个客户端级的audio控件需要开放出的控制接口众多,(vtuber-music项目当前就开放出了13种控制接口),下面贴了张图感受一下,实际上另外那些控制接口实现方式大同小异,都是同 播放暂停 一样的逻辑思路实现的

4b1676af421412408880c92158822447.png

原谅我并没有对如何处理vuex的watch性能瓶颈问题在文章里大书笔墨(因为太复杂了并且方式繁多),而且在完成抽象操作之后,诸如歌单歌曲切换,歌词滚动这些功能仍需很大的工作量,我就不在这里一一赘述,并且在vtuber-music这个开源项目目前已经有比较完善的实现和详细的文档了,有兴趣的话可以尝试clone下来看看那。


- END -

d7db85d60ec2b370f2fd974b84388f36.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值