不知不觉埋头于业务已许久,距离上一篇分享应该很久很久以前,具体何时,已无从知晓。慢慢的开始觉得锅有点热,感觉呼吸有点困难,温水里面的青蛙趁着腿还没完全麻木的时候,也想着开始重拾旧梦,稍微往上蹬蹬,好了,废话就不多提了,时不时的低头总结某段时间的成果大过于做10个新项目,下面就开始慢慢的总结之路吧!(ps:其实是往事不堪回首)

前置背景介绍:公司前期比较倾向于以小程序作为开始,打开市场,于是就开始十分漫长的开发之路(ps: 其实一开始是拒绝的,因为个人原因不太喜欢这种跟所有的东西都搭点边,但又有各种枷锁限制的东西,开发起来不爽,总感觉跟有些东西神似或型似,优化起来更不爽,因为有很多根本就没给你开这种权限),不知不觉有开始废话连篇了,哈哈,完结。

解决的问题:避免同一个页面内,多个音视频同时在播放,造成多个重音的bug。解决记录当前播放音频进度,当组件没有销毁时,可以随意拖动进度条进行播放,也可以来回切换,进度条不会丢失。

框架:uniapp,一种跨平台的解决方案,跨着跨着到最后又走向了一方,尝试过打包成app,甚至还上个应用市场,总体来说对于一些小应用来说,其实是完全够用的,这一点可以肯定,节省了很多的开发成本,感兴趣的同学可以去了解下,但是坑也是有点滴。所以后面源码的结构可能有点怪,用其他框架开发的,可以稍许转化下。

音频api: 由于小程序把原有的音频组件删除了,音频是通过api实例化出来,所以音视频多媒体管理器,主要是对音频的再封装。阅读微信文档可以发现主要有两个:createInnerAudioContextgetBackgroundAudioManager
createInnerAudioContext:不用多提,用过的同学都知道这可能是史前巨坑,各种问题,具体有多坑,请查阅微信开发社区,满满皆是。
getBackgroundAudioManager:背景音乐播放器,支持后台播放,最大的区别就是播放后会有个状态,显示在你手机的系统状态里面,微信的右边会有的小的播放状态icon,播放微信聊天语音时会中断,完毕之后又会继续,是一个非常强大的api,坑位比较少,目前采用的是这种,因为上者安卓和ios两端的兼容性问题,简直是层出不穷,眼火缭乱,不得已,弃之。

multi-media-manager.ts 多媒体订阅类,所有的多媒体订阅这个类,就可以统一处理,不会存在视音频等一起播放,造成多重背景声音的问题,也就是整个小程序只有一个声音在播放。key:这个属性其实可以删除,因为就用url作为订阅的key就可以了,但是会有个问题,就是你从列表页播放了个音频,进入详情页其实那个音频的进度跟列表页的是一样的,但是反过来想,也没什么问题,毕竟是同一个音频,有进度条的记录没什么关系。callback:由于使用的是uniapp,又是基于vue的,所以我底下所牵涉到的类都过了一遍vue.observe的,也就是继承了Store这个类,数据是响应式的,没必要传入callback,没用到的同学把继承Store去掉,可以传入callback。

/*** 全局多媒体订阅*/import { Store } from '../vuex';
import { BackgroundAudio } from './background-audio';/*** 音频类*/export interface SubscribeItem {key: string; // 订阅keysrc: string; // 资源地址type: 'video' | 'audio'; // 媒体类型el?: VideoContext; // 当前视频实例duration?: number; // 媒体时长isPlaying?: boolean;callback?: () => void; // 回调函数seekTime?: number; // 设置播放开始时间
}export interface RealSubscribeItem {key: string; // 订阅keysrc: string; // 资源地址type: 'video' | 'audio'; // 媒体类型el?: VideoContext; // 当前视频实例duration: number; // 媒体时长isPlaying: boolean;count?: number;callback?: () => void; // 回调函数seekTime: number; // 设置播放开始时间
}/*** 多媒体类*/
export default class MultiMediaManager extends Store {// 音频public audio = new BackgroundAudio((time, duration, type) =>this.audioCallback(time, duration, type));// 视频public subscribeList: RealSubscribeItem[] = [];// 获取正在播放的资源public get currentItem() {const arr = this.subscribeList.filter((i) => i.isPlaying);if (!arr.length) return null;return arr[0];}// 检测keyprivate checkKey(key: string) {if (!key) throw Error('请传入订阅key值');if (this.subscribeList.findIndex((i) => i.key === key) === -1)throw Error(`此key:${key}没有进行订阅`);}// 获取自身参数public getSelfParams(key: string): RealSubscribeItem | null {const arr = this.subscribeList.filter((item) => {return item.key === key;});if (!arr.length) return null;return arr[0];}// 音频播放回调public audioCallback(time: number, duration: number, type?: string) {this.subscribeList = this.subscribeList.map((item) => {if (item.isPlaying) {// 播放结束则停止if (type === 'end') {item.seekTime = item.duration;setTimeout(() => {item.isPlaying = false;item.seekTime = 0;}, 300);} else if (type === 'stop') {item.isPlaying = false;} else {item.seekTime = time;}item.callback && item.callback(item);// 时长校验 - 如果接口返回,或者已知的时长不正确,可以开启重新校验赋值// item.duration !== duration &&//  duration &&//   (item.duration = duration);}return item;});}// 播放public $play(key: string, time?: number) {this.checkKey(key);this.subscribeList = this.subscribeList.map((item) => {item.isPlaying = item.key === key;if (item.key === key && item.type === 'audio') {typeof time !== 'undefined' && (item.seekTime = time);// 有播放记录的,则seek,无则playif (typeof item.seekTime !== 'undefined') {this.audio.$seek(item.src, item.seekTime);} else {this.audio.$play(item.src);}}// 未选中视频,则暂停所有视频播放if (item.key !== key && item.type === 'video') {item.el && item.el.pause();}// 选中视频时,清除音频监听if (item.key === key && item.type === 'video') {this.audio.$pause();}return item;});console.log('play订阅列表', this.subscribeList);}// 暂停public $pause(key: string) {this.checkKey(key);this.subscribeList = this.subscribeList.map((item) => {if (item.key === key && item.isPlaying && item.type === 'audio') {this.audio.$pause();item.seekTime = this.audio.bgAudioMannager.currentTime || 0;item.isPlaying = false;}if (item.key === key && item.isPlaying && item.type === 'video') {item.el && item.el.pause();item.isPlaying = false;}return item;});console.log('pause订阅列表', this.subscribeList);}// 暂停所有public $pauseAll() {this.subscribeList = this.subscribeList.map((item) => {if (item.isPlaying) {if (item.type === 'audio') {this.audio.$pause();item.seekTime = this.audio.bgAudioMannager.currentTime || 0;} else {item.el && item.el.pause();}}item.isPlaying = false;return item;});}// 切换public $toggle(key: string) {this.checkKey(key);this.subscribeList = this.subscribeList.map((item) => {if (item.key === key && item.type === 'audio' && item.isPlaying) {this.$pause(key);} else if (item.key === key &&item.type === 'audio' &&!item.isPlaying) {this.$play(item.key);}return item;});}// 订阅public $subscribeManager(item: SubscribeItem) {const { key = '' } = item;if (!key) throw Error('请传入订阅key值');const index = this.subscribeList.findIndex((i) => i.key === key);if (index !== -1) {(this as any).subscribeList[index].count++;return;}const realItem = Object.assign({isPlaying: false,seekTime: 0,duration: 0,count: 1,},item);this.subscribeList.push(realItem);}// 取消public cancel(key: string) {const index = this.subscribeList.findIndex((i) => i.key === key);if (index === -1) return;if (this.subscribeList[index].type === 'audio') {this.audio.$stop();(this as any).subscribeList[index].count--;if (!this.subscribeList[index].count) {this.subscribeList.splice(index, 1);}return;}this.subscribeList.splice(index, 1);// if (!this.subscribeList.length) {//   this.destory();// }}// 销毁public destory() {this.subscribeList = [];}
}

audioManager.ts

import { Store } from '../vuex';export type AudioCallback = (time: number,duration: number,type?: string
) => void;export class BackgroundAudio extends Store {// 背景音乐管理器public bgAudioMannager: BackgroundAudioManager = uni.getBackgroundAudioManager();public callback?: AudioCallback;public timer: any = 0;public constructor(callback?: AudioCallback) {super();this.callback = callback;this.addListener();}public addListener() {this.bgAudioMannager.onPlay(() => {console.log('=========== onPlay ===============');});this.bgAudioMannager.onWaiting(() => {console.log('=========== onWaiting ===============');});this.bgAudioMannager.onCanplay(() => {console.log('=========== onCanplay ===============');this.bgAudioMannager.play();});(this as any).bgAudioMannager.onSeeking(() => {console.log('=========== onSeeking ===============');});(this as any).bgAudioMannager.onSeeked(() => {console.log('=========== onSeeked ===============');this.bgAudioMannager.play();});this.bgAudioMannager.onTimeUpdate((res) => {if (this.timer) return;this.timer = setTimeout(() => {clearTimeout(this.timer);this.timer = 0;}, 300);const { currentTime = 0, duration = 0 } = this.bgAudioMannager;this.callback && this.callback(currentTime, duration);});this.bgAudioMannager.onPause(() => {console.log('=========== onPause ===============');});this.bgAudioMannager.onStop(() => {console.log('=========== onStop ===============');const { duration = 0 } = this.bgAudioMannager;this.callback && this.callback(0, duration, 'stop');});this.bgAudioMannager.onEnded(() => {console.log('=========== onEnded ===============');const { duration = 0 } = this.bgAudioMannager;this.callback && this.callback(duration, duration, 'end');});}public $play(src: string) {console.log('正常play');this.callback && this.callback(0, 0);this.bgAudioMannager.title = '标题';this.bgAudioMannager.startTime = 0;this.bgAudioMannager.src = src;this.bgAudioMannager.play();}public $pause() {this.bgAudioMannager.pause();}public $stop() {this.bgAudioMannager.stop();}public $seek(src: string, time: number) {console.log('正常seek');if (src !== this.bgAudioMannager.src) {console.log('正常seek,且url不相等');this.bgAudioMannager.title = '标题';this.bgAudioMannager.startTime = time;this.bgAudioMannager.src = src;}(this as any).bgAudioMannager.seek(time);}
}

音频.vue 换成对应框架的文件,最好把音视频相关的封装封装成一个基本组件统一处理。进度条控件推荐使用自带组件slide,具体怎么转化,同学们可以集思广益,大部分的源码就不全贴出来了,可以底下评论交流

public async mounted() {this.mediaStore.$subscribeManager({key: this.mediaKey,src: this.url,type: 'audio',duration: this.sourceDuration,});await this.$nextTick();// 自动播放if (this.autoplay && this.mediaKey)this.mediaStore.$toggle(this.mediaKey);
}
// 页面销毁的时候,解除订阅
public beforeDestroy() {this.mediaStore.cancel(this.mediaKey);
}

视频.vue

<videoclass="model-video"id="myVideo":poster="firstPic.poster":src="url":controls="true":show-center-play-btn="false"@play="onVideoPlay"@pause="onVideoPause"
></video>
public async mounted() {await this.$nextTick();this.videoEl = uni.createVideoContext('myVideo', this);this.mediaStore.$subscribeManager({key: this.mediaKey,src: this.url,type: 'video',el: this.videoEl});
}
public beforeDestroy() {this.mediaStore.cancel(this.mediaKey);
}

同学们可以完全不按照这种来,因为也可能存在一些缺陷和漏洞,提供一种管理的思路,也有可能有些小伙伴根本就不会存在这么复杂的情况,我们的情况比较特殊,整个feed流比较复杂,导致不仅这些媒体难管理,性能瓶颈也是一个极大的问题。
       以上只贴出了一些关键性的代码,还有问题的小伙伴可以底下评论我,目前并没有单独的github demo库,但是亲测两端的兼容性还是挺好的。

基于vue的h5音频播放器:https://github.com/Vitaminaq/vue-audios
支持上述的一切功能,开发h5的同学可以用下,欢迎fork加功能。
演示图片:

微信小程序,分享如何封装一个全局支持暂停,拖动等各类操作的音视频等多媒体管理器相关推荐

  1. 新增微信小程序、WebRTC连麦直播多项能力,即构实时音视频SDK再升级

    经过2018年小半年的闭关练功,即构ZEGO团队铸造了不少黑科技.本文将为你带来即构ZEGO实时语音视频SDK近半年新增能力和功能优化的最新进展. 更懂应用场景的语音视频云 作为全球领先的实时语音视频 ...

  2. 详解 - 解决微信小程序分享功能图片比例问题 - 全局分享

    前言: 我在我的博客小程序使用微信小程序分享功能 图片不符合5:4问题 ,对其原理 扫描下面二维码,可以体验哦 准备 在需要自定义分享的页面 设置canvas 组件 目录 准备 详解思路 定义总函数 ...

  3. 微信小程序canvas2d使用封装与案例使用

    微信小程序canvas2d使用封装与案例使用,看一下这边封装效果 canvas2d文档:https://www.canvasapi.cn/ 下载地址:https://download.csdn.net ...

  4. uniapp 微信小程序登录方法封装

    uniapp 微信小程序登录方法封装 前言 一.登录接口 二.登录 上代码 总结 前言 ui设计没有登录页所以将微信小程序登录方法同一封装一个方法 一.登录接口 uni.getUserProfile ...

  5. 微信小程序分享二维码生成

    生成微信小程序分享二维码 微信小程序官方开放的二维码的接口,其中有一个是生成小程序二维码的,还有一个是圆形的小程序码,我这里就用php生成二维码. 完整代码: public function shar ...

  6. 微信小程序网址请求封装

    由于懒得改header,所以method的请求要求必须大写 封装代码引入全局里面,其他页面引入调用即可 //封装微信小程序网络i请求的全局方法// url 请求地址// method get还是pos ...

  7. golang-vue实现微信小程序分享到朋友圈

    最近涉及到微信小程序分享到朋友圈,不知道微信为什么不直接接口分享,咱也不敢佛,咱也不敢问,只能百度问度娘,看官方文档,网上的一些分享五花八门,每一个重点的,所以整理了一下到底怎样生成二维码分享图片才是 ...

  8. android微信分享走小程序流程,教你怎么把微信小程序分享到朋友圈

    教你怎么把微信小程序分享到朋友圈 2020年07月13日 | 萬仟网移动技术 | 我要评论 7月8日收到邀请,可将小程序页面分享到朋友圈.适用于内容型页面的分享,不适用于有较多交互的页面分享.该功能为 ...

  9. 微信小程序分享功能开发及调试方法

    首先说一下使用方法如下,Button组件设置open-type="share"即可触发onShareAppMessage完成分享功能 <button class=" ...

最新文章

  1. t-top 命令详解
  2. 9月16号晚上,Asuka有一场关于Windows 7组策略的Webcast,欢迎兄弟们来捧场
  3. D - Bear and Finding Criminals
  4. Verilog HDL语言设计实现过程赋值+译码器
  5. 重磅!Uber发布史上最简单的深度学习框架Ludwig!不懂编程也能玩转人工智能
  6. 微信又可以发520红包,还新增情人节“撒狗粮”状态:律师提醒注意
  7. oracle:10g下载地址(转载)
  8. 操作系统原理(三)进程管理、线程、并发和并行
  9. mysql基于时间盲注_MySQL基于时间盲注(Time-Based Blind SQL Injection)五种延时方法...
  10. SAP ABAP开发从入门到精通——第15章 面向对象ALV
  11. 获取access_token报错errcode: 40125,errmsg: invalid appsecret, view more at http://t.cn/RAEkdV
  12. 风云唐太宗(上部)精要
  13. 集成学习-Stacking与Blending与泰坦尼克号特征工程(DataWhale第二期)
  14. vue 生成二维码海报并进行微信分享
  15. async和await的用法
  16. RTL8723BU移植
  17. itextpdf 怎么下划线_iText的新字体用粗体部分和下划线
  18. 《STM32从零开始学习历程》——CAN通讯协议协议层
  19. 训练softmax分类器实例_吴恩达深度学习笔记(56)-训练一个 Softmax 分类器
  20. ES索引的生命周期管理

热门文章

  1. IDEA 2021一键修改文件后缀名方法
  2. HDS新解决方案建设更安全、更智能及更健康的社会
  3. matlab单双极性眼图程序,求通信大神讲讲这个matlab程序每一段的意思
  4. jni使用(四)-----IDEA中javah生成.h文件
  5. java 服务器 http请求_Java网络编程——发送HTTP请求到服务器
  6. 图形使用计算机绘制的画面,本章练习优质课教学设计
  7. BIM的发展现状与技术优势
  8. 会员营销中,沉寂会员的三种运营策略
  9. Markdown 插入图片 基于base64编码
  10. 安装BT5 backtrack5 linux 无线网卡驱动