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

本文介绍了在H5开发中遇到的两个问题及其解决方案:一是当页面切到后台时,语音仍然播放;二是部分安卓设备要求音频播放必须由用户手势触发。针对这些问题,开发者通过监听visibilitychange事件来控制后台播放,并考虑了用户手势触发的限制,提出了两种应对策略。此外,还展示了如何同步语音和文字滚动效果,以及实现了暂停功能的思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求与bug解决

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

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

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

  mounted() {
    this.audioTemp = new Audio(AthenaData.audioList[0].voice);

   
    // 切到后台停止语音
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        this.musicDisabled = false; // 按钮状态
        this.audioTemp.pause();
      }
    });
  },

bug2: API can only be initiated by a user gesture

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

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

功能实现

template:

        // 播放按钮       
         <div
          v-image.normal="musicDisabled ? AthenaData.horn1 : AthenaData.horn2"
          class="horn1"
          @click="onBgmMusicClick"
        />
         

        // 滚动文字
         <div
            ref="box"
            class="tips-box"
          >
            <div
              ref="content"
              class="tips-content"
              :style="tranlateDo?{
                transition: `all ${tranlateTime}s linear`,
                transform: `translateX(-${tranlatex}px)`,
              }:{
                transform: 'translateX(0)'
              }"
            >
              {{ AthenaData.audioList[voiceIndex].label }}
            </div>
          </div>

data:

  data() {
    return {
      AthenaData,  // 数据
      musicDisabled: false,  // 播放按钮状态
      audioTemp: null,  // 语音对象
      voiceIndex: 0,  // 文字索引
      tranlateDo: false,  // 文字滚动状态
      tranlatex: 0,     // 文字滚动距离
      
      timeoutTicket: null,  // 当前文字
      voiceTimeoutTicket: null,  // 下一段文字
    };
  },

mounted:


  mounted() {

    // 设置语音播放列表
    this.audioTemp = new Audio(AthenaData.audioList[0].voice);
    this.audioTemp.onended = () => {
      this.musicDisabled = false;
      this.audioTemp.pause();
      this.audioTemp.load();
      this.tranlateDo = false;
      this.voiceIndex = 0;
      clearTimeout(this.voiceTimeoutTicket);
    };

    // 切到后台停止语音
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        this.musicDisabled = false;
        this.audioTemp.pause();
        this.tranlateDo = false;
        this.voiceIndex = 0;
        this.audioTemp.load();
        if (this.timeoutTicket) {
          clearTimeout(this.timeoutTicket);
        }
        clearTimeout(this.voiceTimeoutTicket);
      }
    });
  },

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

 methods: 

  methods: {
    // 播放语音按钮回调
    onBgmMusicClick() {
      this.musicDisabled = !this.musicDisabled;
      if (this.musicDisabled && AthenaData.audioList.length) {
        this.audioTemp.play();
        this.setLabelPlay();
      }
      if (!this.musicDisabled) {
        this.audioTemp.pause();
        this.tranlateDo = false;
        this.voiceIndex = 0;
        this.audioTemp.load();
        if (this.timeoutTicket) {
          clearTimeout(this.timeoutTicket);
        }
        clearTimeout(this.voiceTimeoutTicket);
      }
    },


    //设置文字动画
    setLabelPlay() {
    // 语音有半秒前奏播放完后 再开始文字动画
      this.timeoutTicket = setTimeout(() => {
        this.tranlateDo = true;
        this.tranlatex = this.$refs.content.offsetWidth - this.$refs.box.offsetWidth;
      }, 500);

      
      clearTimeout(this.voiceTimeoutTicket);
      this.voiceTimeoutTicket = setTimeout(() => {
// 目前只有三段文字,若索引为2说明到最后一段,置0停掉动画;否则处理下一段(+1后则显示下一段文字)
        this.voiceIndex = this.voiceIndex === 2 ? 0 : this.voiceIndex + 1;  
// 以下为控制下一段文字动画的逻辑
        if (this.voiceIndex !== 0) {
// 停掉动画 从头滚动
          this.tranlateDo = false;
          this.tranlatex = 0;
// 开始滚动
          this.setLabelPlay();
        } else {
          clearTimeout(this.voiceTimeoutTicket);
        }
      }, parseInt(AthenaData.audioList[this.voiceIndex].time, 10) * 1000); // 这个时间为当前动画持续的时间
    },
    



}

最终效果如下,资源链接

屏幕录制2022-07-28 18.51.06

      

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

 

Audio介绍

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

Audio对象的属性

Audio对象的方法 

 Audio对象的事件

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白目

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值