实现在同一个页面上展示和控制多个音频源,同时确保全局只有一个音频在播放。
- 核心需求
我们的音频播放器需要满足以下关键需求:
- 在同一个页面支持多个音频播放器实例
- 每个实例可以独立暂停和继续
- 全局同一时间只允许一个音频播放
- 切换播放时自动暂停当前正在播放的音频
- 实现策略
为了满足这些需求,我们采用了以下策略:
a. 组件级别的音频控制: 使用useAudioPlayer自定义hook为每个音频实例提供独立的控制逻辑。
b. 全局状态管理: 通过Vuex store管理全局的播放状态,确保同一时间只有一个音频在播放。
c. 状态同步机制: 在本地播放控制和全局状态之间建立双向同步机制。
useAudioPlayer.js:
import { ref, isRef, onMounted, onUnmounted, computed, watch } from "vue";
import { useStore } from "vuex";
import Taro from "@tarojs/taro";
import { toRaw } from "vue";
export function useAudioPlayer(voiceUrl) {
const audioSrc = computed(() => voiceUrl.value[0]);
const store = useStore();
const audioContext = Taro.createInnerAudioContext();
watch(
audioSrc,
(newSrc) => {
if (newSrc) {
audioContext.value = Taro.createInnerAudioContext();
console.log("newSrc", newSrc);
audioContext.value.src = newSrc;
audioContext?.value?.onEnded(() => {
store.dispatch("pause");
});
}
},
{ immediate: true }
);
const isCurrentlyPlaying = computed(() => {
const rawCurrentAudio = toRaw(store.state.currentAudio);
console.log("Raw current audio:", rawCurrentAudio);
console.log("Audio context:", audioContext);
return store.state.isPlaying && rawCurrentAudio === audioContext.value;
});
const play = () => {
if (toRaw(store.state.currentAudio) !== audioContext.value) {
store.dispatch("pause");
store.dispatch("setCurrentAudio", audioContext.value);
}
store.dispatch("play");
};
const pause = () => {
if (toRaw(store.getters.currentAudio) === audioContext.value) {
store.dispatch("pause");
}
};
onUnmounted(() => {
if (store.getters.currentAudio === audioContext.value) {
audioContext.value.stop();
store.dispatch("pause");
store.dispatch("setCurrentAudio", null);
}
audioContext.value.destroy();
});
return {
isCurrentlyPlaying,
play,
pause,
};
}
关键点解析:
- 每个
useAudioPlayer实例创建自己的audioContext,允许独立控制。 play函数在播放新音频前会检查并暂停当前播放的音频,确保全局只有一个音频在播放。pause函数只在当前实例是正在播放的音频时才执行暂停,避免影响其他实例。
store.js:
/* eslint-disable no-shadow */
import { createStore } from "vuex";
import { toRaw } from "vue";
const state = {
currentAudio: null,
isPlaying: false,
};
const mutations = {
SET_CURRENTAUDIO(state, audio) {
state.currentAudio = audio;
},
SET_ISPLAYING(state, isPlaying) {
state.isPlaying = isPlaying;
},
};
const actions = {
setCurrentAudio({ commit }, audio) {
console.log("SET_CURRENTAUDIO", audio);
commit("SET_CURRENTAUDIO", audio);
},
play({ commit, state }) {
if (state.currentAudio) {
toRaw(store.state.currentAudio)?.play();
commit("SET_ISPLAYING", true);
}
},
pause({ commit, state }) {
if (state.currentAudio) {
toRaw(store.state.currentAudio)?.pause();
commit("SET_ISPLAYING", false);
}
},
};
const getters = {
currentAudio(state) {
return state.currentAudio;
},
isPlaying(state) {
return state.isPlaying;
},
};
const store = createStore({
state,
mutations,
actions,
getters,
});
export default store;
关键点解析:
currentAudio状态用于跟踪当前正在播放的音频实例。setCurrentAudio动作允许切换当前活动的音频实例。play和pause动作统一管理全局的播放状态,确保状态的一致性。
- 实现多音频独立控制的核心机制
a. 实例隔离: 每个useAudioPlayer实例都有自己的audioContext,使得每个音频源可以独立控制。
b. 全局状态同步: 通过Vuex store的currentAudio状态,我们可以跟踪当前活跃的音频实例。
c. 播放切换逻辑: 在play函数中,我们首先检查并暂停当前播放的音频,然后再设置新的当前音频并开始播放。
d. 条件性暂停: pause函数只在当前实例是活跃音频时才执行暂停,这允许其他非活跃实例保持其状态。