• 安卓机调用 audio.play()时 报错:API can only be initiated by a user gesture


    需求与bug解决

            做 H5开发的一个需求:页面内有一个按钮点击可以播放语音,产品提供的素材是多段语音,并配有对应文字;

    bug1: 切换到后台时,语音还在播放,

    解决方法:增加 visibilitychange 监听事件,用 document.hidden 判断是否处于后台。

    1. mounted() {
    2. this.audioTemp = new Audio(AthenaData.audioList[0].voice);
    3. // 切到后台停止语音
    4. document.addEventListener('visibilitychange', () => {
    5. if (document.hidden) {
    6. this.musicDisabled = false; // 按钮状态
    7. this.audioTemp.pause();
    8. }
    9. });
    10. },

    bug2: API can only be initiated by a user gesture

    原因:部分安卓机要求调用 audio.play() 这个接口必须要用户手势触发,也就是必须是真实的用户操场类似 click之类的事件去触发才行,用js主动去触发dom的click的话也是无效的!

    解决方案有二:1. 安卓端上有对应的 api 设置可以取消这个限制,但一定会有合规方面的风险(法务检测那边可能无法通过)2.将多段语音合成一段。

    功能实现

    template:

    1. // 播放按钮
    2. v-image.normal="musicDisabled ? AthenaData.horn1 : AthenaData.horn2"
    3. class="horn1"
    4. @click="onBgmMusicClick"
    5. />
    6. // 滚动文字
    7. <div
    8. ref="box"
    9. class="tips-box"
    10. >
    11. <div
    12. ref="content"
    13. class="tips-content"
    14. :style="tranlateDo?{
    15. transition: `all ${tranlateTime}s linear`,
    16. transform: `translateX(-${tranlatex}px)`,
    17. }:{
    18. transform: 'translateX(0)'
    19. }"
    20. >
    21. {{ AthenaData.audioList[voiceIndex].label }}
    22. div>
    23. div>

    data:

    1. data() {
    2. return {
    3. AthenaData, // 数据
    4. musicDisabled: false, // 播放按钮状态
    5. audioTemp: null, // 语音对象
    6. voiceIndex: 0, // 文字索引
    7. tranlateDo: false, // 文字滚动状态
    8. tranlatex: 0, // 文字滚动距离
    9. timeoutTicket: null, // 当前文字
    10. voiceTimeoutTicket: null, // 下一段文字
    11. };
    12. },

    mounted:

    1. mounted() {
    2. // 设置语音播放列表
    3. this.audioTemp = new Audio(AthenaData.audioList[0].voice);
    4. this.audioTemp.onended = () => {
    5. this.musicDisabled = false;
    6. this.audioTemp.pause();
    7. this.audioTemp.load();
    8. this.tranlateDo = false;
    9. this.voiceIndex = 0;
    10. clearTimeout(this.voiceTimeoutTicket);
    11. };
    12. // 切到后台停止语音
    13. document.addEventListener('visibilitychange', () => {
    14. if (document.hidden) {
    15. this.musicDisabled = false;
    16. this.audioTemp.pause();
    17. this.tranlateDo = false;
    18. this.voiceIndex = 0;
    19. this.audioTemp.load();
    20. if (this.timeoutTicket) {
    21. clearTimeout(this.timeoutTicket);
    22. }
    23. clearTimeout(this.voiceTimeoutTicket);
    24. }
    25. });
    26. },

    1. computed: {
    2. // 读当前文字滚动的时间
    3. tranlateTime() {
    4. // 三目运算符容错
    5. return AthenaData.audioList[this.voiceIndex]
    6. ? AthenaData.audioList[this.voiceIndex].time - 1// 每段语音的首末有半秒背景音乐
    7. : 1;
    8. },
    9. },

     methods: 

    1. methods: {
    2. // 播放语音按钮回调
    3. onBgmMusicClick() {
    4. this.musicDisabled = !this.musicDisabled;
    5. if (this.musicDisabled && AthenaData.audioList.length) {
    6. this.audioTemp.play();
    7. this.setLabelPlay();
    8. }
    9. if (!this.musicDisabled) {
    10. this.audioTemp.pause();
    11. this.tranlateDo = false;
    12. this.voiceIndex = 0;
    13. this.audioTemp.load();
    14. if (this.timeoutTicket) {
    15. clearTimeout(this.timeoutTicket);
    16. }
    17. clearTimeout(this.voiceTimeoutTicket);
    18. }
    19. },
    20. //设置文字动画
    21. setLabelPlay() {
    22. // 语音有半秒前奏播放完后 再开始文字动画
    23. this.timeoutTicket = setTimeout(() => {
    24. this.tranlateDo = true;
    25. this.tranlatex = this.$refs.content.offsetWidth - this.$refs.box.offsetWidth;
    26. }, 500);
    27. clearTimeout(this.voiceTimeoutTicket);
    28. this.voiceTimeoutTicket = setTimeout(() => {
    29. // 目前只有三段文字,若索引为2说明到最后一段,置0停掉动画;否则处理下一段(+1后则显示下一段文字)
    30. this.voiceIndex = this.voiceIndex === 2 ? 0 : this.voiceIndex + 1;
    31. // 以下为控制下一段文字动画的逻辑
    32. if (this.voiceIndex !== 0) {
    33. // 停掉动画 从头滚动
    34. this.tranlateDo = false;
    35. this.tranlatex = 0;
    36. // 开始滚动
    37. this.setLabelPlay();
    38. } else {
    39. clearTimeout(this.voiceTimeoutTicket);
    40. }
    41. }, parseInt(AthenaData.audioList[this.voiceIndex].time, 10) * 1000); // 这个时间为当前动画持续的时间
    42. },
    43. }

    最终效果如下,资源链接

    屏幕录制2022-07-28 18.51.06

          

      其实,不难发现,语音和文字的单独控制,只不过是卡住时间让效果看起来是同步。除此之外,还可以扩充暂停功能:播放语音中途点击按钮让语音和文字滚动暂停,再次点击时接着上述效果。原生实现方法只需增加一个变量记录点击时文字已滚动的距离,然后再次点击时进行判断,让动画接着滚动;如果引入其他动画库也许会有直接的API控制动画的暂停,实现起来更加简单。

     

    Audio介绍

            什么?你不知道audio是什么?它是h5新特性之一,audio可以理解为普通的dom对象,相关属性方法事件如下:

    Audio对象的属性

    Audio对象的方法 

     Audio对象的事件

            事件和方法很像,只不过是可以传入回调函数增加更多逻辑

  • 相关阅读:
    MySQL 特殊字符
    springMVC执行流程详解
    静态HTML网页设计作品 DIV布局家乡介绍网页模板代码---(太原 10页带本地存储登录注册 js表单校验)
    Metabase学习教程:仪表盘-4
    基于PyQt5和PSoC6的多功能平衡车设计
    【带你学c带你飞】1day 第2章 (练习2.2 求华氏温度 100°F 对应的摄氏温度
    制作一个简单HTML个人网页网页——人物介绍梵高(HTML+CSS)
    Unity中Shader需要了解的点与向量
    云原生技术实践:Kubernetes集群的部署与运维
    利用Jdk动态代理模拟MyBatis的Mapper功能
  • 原文地址:https://blog.csdn.net/qq_37974755/article/details/126036916