最近工作需要遇到一个挺离谱的问题,我使用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:如果有能帮我解答一下具体为什么会出现这样情况那就更好啦