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

article/2025/8/15 6:22:07

       不知不觉埋头于业务已许久,距离上一篇分享应该很久很久以前,具体何时,已无从知晓。慢慢的开始觉得锅有点热,感觉呼吸有点困难,温水里面的青蛙趁着腿还没完全麻木的时候,也想着开始重拾旧梦,稍微往上蹬蹬,好了,废话就不多提了,时不时的低头总结某段时间的成果大过于做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加功能。
演示图片:
演示图片


http://chatgpt.dhexx.cn/article/0cHOUY0j.shtml

相关文章

html微信登录密码输入密码,不用输密码,只要8个数字就能登录你的微信

不知道大家有没有遇到过这样的问题&#xff0c;就是当我们换新手机之后&#xff0c;在登录微信的时候就需要密码了。 打开凤凰新闻&#xff0c;查看更多高清图片 可是有些人记性不好&#xff0c;经常忘记微信登录密码&#xff0c;这该怎么办呢&#xff1f;今天小Q来教大家一招可…

微信上隐藏着6个功能,实在是太厉害了,真令人相见恨晚

很多人都说微信不太好用,那是因为你还没有找到好用的功能,如果你只会聊天和付款那就太out了,今天才知道,微信上隐藏着6个功能实在是太厉害了。 一、无线极速传输 微信不仅方便了生活,在办公上也是一个小能手,无需数据线就能与电脑相互传输文件,找到文件传输助手,将文件…

基于微信小程序音乐播放器的设计与实现毕业设计源码271156

Springboot音乐播放小程序的设计与实现 摘 要 本文设计了一种基于微信小程序的音乐播放器&#xff0c;系统为人们提供了方便快捷、即用即搜的音乐搜索播放服务&#xff0c;包括音乐资讯、音乐库推荐、交流论坛、注册登录、最近播放列表功能等&#xff0c;用户不仅能够方便快捷地…

微信按钮翻译中英对照表

申明&#xff1a;发文方便自己查阅&#xff0c;当然如对看到的朋友有所帮助&#xff0c;也是乐事一件。 代码中需要调用微信&#xff0c;翻译连续翻车&#xff0c;比如朋友圈&#xff0c;做个记录方便查阅&#xff1a; /** * 微信 中英文对照 * 微信 wechat * 朋友圈…

微信更新了 版本8.0.30

这次新功能包括&#xff1a; 朋友圈扩展文字输入区域&#xff1a;发朋友圈或朋友圈评论时&#xff0c;编辑文案&#xff0c;文本区域会随文字数量而增大。长按订阅号可以取消关注&#xff1a;在订阅号列表中&#xff0c;此前长按封面只有悬浮功能&#xff0c;更新后现在加入了…

android微信消息无提醒,OPPO手机微信消息不提醒怎么办?(附多种解决方法)

微信来新消息不提醒&#xff0c;不能第一时间看到微信消息&#xff0c;一般是设置不到位。现在就可以通过以下几步来进行排除解决。 小编以R9手机为例&#xff0c;这就告诉你们相关设置哈&#xff0c;一起来学习吧~~ OPPO手机微信消息不提醒怎么办&#xff1f;(附多种解决方法)…

ipad如何与手机微信连接服务器地址,如何设置微信在手机和ipad同步 - 卡饭网

微信怎么设置空白头像和昵称?iPhone手机设置微信空白头像和昵称的方法介绍 微信怎么设置空白头像和昵称?iPhone手机设置微信空白头像和昵称的方法介绍 iPhone手机应该如何设置微信空白名字和头像?微信头像往往能反映出一个人的性格,让自己的微信头像和别人不一样,彰显出自己…

苹果微信密码服务器,苹果微信怎么记住密码的登录

满意答案 tanwandewo 2020.09.14 采纳率&#xff1a;48% 等级&#xff1a;6 已帮助&#xff1a;210人 在进行手机各类密码设置的时候&#xff0c;总是会有一种这辈子不会忘的错觉&#xff0c;于是怎么复杂怎么来&#xff0c;忘记密码就崴泥了&#xff0c;悔恨啊…… 是不是看…

微信能用声音作为密码来登录了,你的应用也可以

自从苹果在iPhone上普及了指纹解锁,以个人生理特征作为密码的生物识别已经成为了越来越流行的身份鉴别手段,冲击着使用多年的传统文字密码方式。 毕竟,在脑袋里记住密码,真的不如用你独一无二的身体特征直接作为密码那么方便。 就说我们每天都在用的微信,现在也赶上这个潮…

模仿微信声音锁的实现(运用DTW算法)

自己写了模仿微信声音锁的一个android程序&#xff0c;先录两段相同的语音信号作为模板&#xff0c;解锁时通过对比输入语音和该两段语音模板的相似度&#xff0c;如果相似度高则解锁成功。 程序界面如下&#xff0c;仿照微信设置&#xff0c;当然跟原版比效果略渣。 launche…

微信声音锁会上传到服务器吗,微信声音锁:你再也不用担心忘记密码了

科客点评&#xff1a;声音登录并不是让用户重新说一遍原先设置的内容&#xff0c;而是可以根据声纹进行自动识别。 你微信密码是怎么设置的?用的QQ 密码?还是单独设立了一个?通常我们不会把微信登出&#xff0c;所以很少用到密码&#xff0c;以至于一旦需要用到微信密码的时…

微信声音锁会上传到服务器吗,微信的声音锁是什么样的操作?

原标题&#xff1a;微信的声音锁是什么样的操作&#xff1f; 说到微信的声音锁也许没有多少人注意过&#xff0c;今天简单介绍一下微信的声音锁功能。 声音锁的操作步骤&#xff1a; 在微信设置→账号与安全→声音锁&#xff0c;然后根据以下操作即可完成。 “声音锁”这个功能…

微信声音锁会上传到服务器吗,微信声音锁安全吗?微信声音锁可以换吗?

大家可能都知道了有一个微信声音锁&#xff0c;那么今天主要要跟大家分析的是微信声音锁安全吗?我想这个大家肯定想知道。那么微信声音锁可以换吗?关于这个问题小编都将在下文中为大家解答。详情请见微信声音锁安全吗?微信声音锁可以换吗? 1.微信声音锁安全吗? 微信声音锁…

微信声音锁会上传到服务器吗,微信声音锁原理是什么?微信声音锁安全吗?

微信声音锁是一种先进的技术&#xff0c;用户可以用声音给手机上锁&#xff0c;听起来还是很猛的哈。 关键词&#xff1a;声纹 所谓声纹(Voiceprint)&#xff0c;是用电声学仪器显示的携带言语信息的声波频谱。现代科学研究表明&#xff0c;声纹不仅具有特定性&#xff0c;而且…

上线三年却很“鸡肋”的微信声音锁究竟做错了什么?

栏目简介&#xff1a;激荡六十年&#xff0c;人工智能已经起航。然而在未来面前&#xff0c;我们都还是孩子。究竟是“奇点临近”&#xff1f;还是泡沫行将破灭&#xff1f;为了解惑&#xff0c;《AI名人堂》将汇聚领航者智慧&#xff0c;和你一起探索前行的方向。 记者 | 杨丽…

微信隐藏功能系列:微信声音锁怎么设置?仅1%的人懂得用“嘴”登录微信

本期微信隐藏功能&#xff0c;教大家一个比较新奇的&#xff0c;微信声音锁怎么设置&#xff1f;用声音识别功能登录微信&#xff0c;相信大家应该没用过吧&#xff0c;平时我们登录微信都是密码或者手机验证登录&#xff0c;这次让大家看看微信声音锁怎么用&#xff01; 当你的…

【密码学】PRP和PRF

PRP&#xff08;pseudo random permutation&#xff0c;伪随机置换&#xff09;和PRF&#xff08;pseudo random function&#xff0c;伪随机函数&#xff09;之间的区别&#xff0c;可以从定义来看 PRF 取一个密钥和集合X中的元素作为输入&#xff0c;输出值在集合Y中&#x…

【论文】使用文本分类的PRF

The Simplest Thing That Can Possibly Work: Pseudo-Relevance Feedback Using Text Classification 论文链接&#xff1a;https://arxiv.org/abs/1904.08861v1 ABSTRACT 本文试图用“最简单的事情”来解决数十年的伪相关反馈问题。 文章提出了一种技术&#xff0c;该技术基…

iOS BLE蓝牙开发数据传输协议详解 常用算法(AES加密 HMAC_hash PRF)

前言 这段时间参与了一款与蓝牙外设交互的项目, 以前没有涉及过数据传输方面的开发, 踩了不少坑, 同时也学到了很多东西. 此时, 项目也即将进入尾声, 有时间把这些记录一二. 本人才疏学浅, 如有错误,大佬轻喷. BLE4.0开发 这方面网上的Demo一大堆, 暂时不做太多的赘述, 只对…

qmake language qt 工程文件 配置文件 .pro .prl .prf .pri 词法 语法 for循环 判断语句 函数定义

目录 词法 string 1、数据类型 2、特殊处理的内置变量 3、转义字符 关键字&#xff1a;包括语法关键字、特殊变量 语法 变量 变量、属性、环境变量 变量使用 全局变量作用域 函数定义和使用 判断语句和循环语句 判断语句 判断语句 条件分支 if 案例 循环语句…