微信小程序ios设备音频播放失败

137 阅读2分钟

最近工作需要遇到一个挺离谱的问题,我使用uniapp做微信小程序,中间需要使用音频播放功能。我这里就写了一个音频播放的hook,这样也方便控制每个页面的音频上下文实例。

这是部分主要函数。
playAudioAsync函数将音频放入队列并开始播放。
playNextInQueue函数是播放队列音频。
stopCurrentAudio是停止当前的音频。

 // 播放音频(默认队列形式)
  const playAudioAsync = async (audioUrl: string): Promise<void> => {
    await ensureInitialized();  // 确保初始化
    console.log('playAudioAsync', audioUrl);

    // 加入队列
    audioQueue.value.push(audioUrl);

    // 如果没有正在播放的音频,则开始播放队列并返回 Promise
    if (!isPlaying.value && !isPlayingQueue.value) {
      return new Promise((resolve, reject) => {
        const tempCallback = (type: AudioEndType, error?: any) => {
          // 移除临时回调
          if (onAudioEndCallback.value === tempCallback) {
            onAudioEndCallback.value = null;
          }

          if (type === AudioEndType.NORMAL || type === AudioEndType.STOPPED) {
            resolve();
          } else {
            reject(error || new Error(`Audio ended with type: ${type}`));
          }
        };

        setOnAudioEnd(tempCallback);
        playNextInQueue().catch(reject);
      });
    } else {
      // 如果已经在播放,立即 resolve(不等待队列中的其他音频)
      return Promise.resolve();
    }
  };
const playNextInQueue = async () => {
    if (audioQueue.value.length === 0) {
      isPlayingQueue.value = false;
      return;
    }

    // 获取并播放队列中的音频
    const url = audioQueue.value[currentQueueIndex.value];

    stopCurrentAudio(); // 停止当前音频

    // 确保音频上下文存在
    if (!audio.value) {
      audio.value = createAudioContext();
    }

    audio.value.src = url; // 设置音频源
    try {
      // 播放音频
      audio.value.play();
      isPlaying.value = true;
    } catch (error) {
      console.error('播放错误:', error);
      uni.showToast({
        title: '播放错误',
        icon: 'error',
      });
    }
  };
// 停止当前音频(不重置队列)
  const stopCurrentAudio = () => {
    if (audio.value) {
      try {
        audio.value.stop();
      } catch (error) {
        console.warn('停止音频时发生错误:', error);
      }
      isPlaying.value = false;
    }
  };

具体业务逻辑就是在页面进入后,会播放一段音频。有的页面是进入后会循环播放几个音频。

这个逻辑在微信开发者工具和安卓设备上是毫无问题的。问题出现在ios设备上。循环播放几个音频中,会发现第一个音频总是没有播放就开始了第二个音频。并且控制台会报错找不到上下文实例。

并且我注意到在onCanplay的监听中,ios设备会执行两次这个监听(这里我不明白是为什么)。最后我发现问题出现在是播放前执行了一次stopCurrentAudio这个函数,好像是这里报错了找不到实例。。然后我把这个函数修改了一下只有当正在播放的时候才会执行stop,报错就消失了,循环播放的第一个音频也能正常播放了。 我修改了这里

const stopCurrentAudio = () => {
    if (audio.value && isPlaying.value) {  // 添加正在播放的判断
      try {
        audio.value.stop();
      } catch (error) {
        console.warn('停止音频时发生错误:', error);
      }
      isPlaying.value = false;
    }
  };

我又自己测试了一下

const test = () => {
  audioContext.value = uni.createInnerAudioContext();

  audioContext.value.onCanplay(() => {
    console.log('onCanplay');
  });

  audioContext.value.onError((e) => {
    console.log('onError', e);
  });
  audioContext.value.stop(); // 这里报错
  audioContext.value.src = '***.mp3';
  setTimeout(() => {
    audioContext.value?.play();
  }, 1000);
};

发现,的确是在stop处报错了找不到实例,并且安卓设备和开发者工具是不会报错的。锁定了问题。

总结:ios设备在使用createInnerAudioContext播放音频时,如果在没有音频播放的前提下调用stop函数会导致找不到上下文实例报错。

ps:如果有能帮我解答一下具体为什么会出现这样情况那就更好啦