切换语言为:繁体

记一次 iOS 音频播放的问题

  • 爱糖宝
  • 2024-11-21
  • 2014
  • 0
  • 0

简单记录一下一个小问题。

背景:h5 hybrid 应用,Vue 项目

需求:App 有背景音乐,h5 页面也有音频播放,当进入页面时设置 App 音频静音,离开页面时设置 App 非静音。

实现代码:

import { showToast } from 'vant'
import { setAudioState } from '@/apis/base'

export function useMusicPlayer(audioElementRef: Ref<HTMLAudioElement | null>): {
  isPlaying: Ref<boolean>
  togglePlay: () => void
} {
  const isPlaying = ref(false)

  /**
   * @function togglePlay
   * @description 切换音乐播放暂停
   * @returns {void}
   */
  function togglePlay(): void {
    const audioElement = audioElementRef.value
    if (audioElement) {
      if (isPlaying.value) {
        audioElement.pause()
      } else {
        // 静音播放,浏览器可能允许
        audioElement.muted = true
        void audioElement.play().catch((error) => {
          console.log('music player error: ', error)
          showToast('音乐播放受限,点击页面以允许播放')
        })
        audioElement.muted = false // 取消静音,用户交互后
      }
      isPlaying.value = !isPlaying.value
    }
  }

  /**
   * @function handleVisibilityChange
   * @description 处理页面可见性事件
   * @returns {void}
   */
  function handleVisibilityChange(): void {
    if (document.hidden) {
      if (isPlaying.value) {
        togglePlay()
      }
    }
  }

  /**
   * @function setAppAudioState
   * @description 设置音频状态
   * @param mute 是否静音
   */
  function setAppAudioState(mute: boolean): void {
    setAudioState({
      mute,
    }).catch((error) => {
      showToast(error.message)
    })
  }

  onMounted(() => {
    document.addEventListener('visibilitychange', handleVisibilityChange)
    setAppAudioState(true)
  })

  onBeforeUnmount(() => {
    document.removeEventListener('visibilitychange', handleVisibilityChange)
    audioElementRef.value?.pause()
    audioElementRef.value = null
    setAppAudioState(false)
  })

  return {
    isPlaying,
    togglePlay,
  }
}

其中 setAudioState 是和原生 App 的通信,设置静音或取消静音。一切都没有问题。

发现的问题:

当我尝试不是在进入页面/离开页面(onMounted/onBeforeUnmount)时设置 App 静音,而是在点击播放/暂停 Web 音频时进行。代码如下:

import { showToast } from 'vant'
import { setAudioState } from '@/apis/base'

export function useMusicPlayer(audioElementRef: Ref<HTMLAudioElement | null>): {
  isPlaying: Ref<boolean>
  togglePlay: () => void
} {
  const isPlaying = ref(false)

  /**
   * @function togglePlay
   * @description 切换音乐播放暂停
   * @returns {void}
   */
  function togglePlay(): void {
    const audioElement = audioElementRef.value
    if (audioElement) {
      if (isPlaying.value) {
        setAppAudioState(false)
        audioElement.pause()
      } else {
        setAppAudioState(true)
        // 静音播放,浏览器可能允许
        audioElement.muted = true
        void audioElement.play().catch((error) => {
          console.log('music player error: ', error)
          showToast('音乐播放受限,点击页面以允许播放')
        })
        audioElement.muted = false // 取消静音,用户交互后
      }
      isPlaying.value = !isPlaying.value
    }
  }

  /**
   * @function handleVisibilityChange
   * @description 处理页面可见性事件
   * @returns {void}
   */
  function handleVisibilityChange(): void {
    if (document.hidden) {
      if (isPlaying.value) {
        togglePlay()
      }
    }
  }

  /**
   * @function setAppAudioState
   * @description 设置游戏音频状态
   * @param mute 是否静音
   */
  function setAppAudioState(mute: boolean): void {
    setAudioState({
      mute,
    }).catch((error) => {
      showToast(error.message)
    })
  }

  onMounted(() => {
    document.addEventListener('visibilitychange', handleVisibilityChange)
  })

  onBeforeUnmount(() => {
    document.removeEventListener('visibilitychange', handleVisibilityChange)
    audioElementRef.value?.pause()
    audioElementRef.value = null
  })

  return {
    isPlaying,
    togglePlay,
  }
}

此时就出现了问题,Web 音频 play 时,能正常设置 App 静音(setAppAudioState(true)),Web 音频 pause 时,不能恢复 App 音量,也就是 setAppAudioState(false) 似乎不起作用。

于是联调,发现这句代码确实执行了,App 也确实收到了信息。而且:

  • 安卓没有问题,暂停能恢复 App 音频设置

  • iPad Air 2019 也没有问题

我测试的有问题的手机是 iPhone 16。

问了一下 ChatGPT,提到了异步相关的问题,setAudioState 返回是一个 Promise,但是 setAppAudioState 的异步执行和 audioElement.pause() 应该相互不影响啊

我将 setAppAudioState(false) 放在定时器中延时执行:

setTimeout(() => {
  setAppAudioState(false)
}, 100)

此时的现象是:Web 音频播放,App 静音;Web 音频暂停,延时一会,App 恢复声音,持续1-2s,App 静音。

很神奇的现象!

我尝试增加延时时长到,1s、2s,恢复音量无效。延时 3s 时才能正常恢复 App 的音量。

  1. 系统音频管理

    • iOS 对音频流的管理非常严格,尤其是在涉及多个音频源时。系统可能会检测到并管理不同的音频源以优化性能和用户体验。例如,当多个音频源同时播放时,系统可能会优先暂停或降低某些音频的音量。

    • 如果 Web 应用和原生应用都在输出音频,系统可能会对其中一个进行调整,这可能解释了在暂停 Web 音频后,App 音量恢复后又很快消失的问题。

  2. 音频焦点

    • iOS 使用音频焦点来管理音频输出,这意味着当一个应用获取音频焦点时,其他音频可能会被暂停或停止。确认你的应用是否正确处理了音频焦点的获取和释放。

  3. 系统 bug 或特性

总之,这个问题,因为不熟悉底层的系统音频播放管理,还不能理解。暂时一记。

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.