Taro多文字转语音播放Hook

375 阅读2分钟

实现在同一个页面上展示和控制多个音频源,同时确保全局只有一个音频在播放。

  1. 核心需求

我们的音频播放器需要满足以下关键需求:

  • 在同一个页面支持多个音频播放器实例
  • 每个实例可以独立暂停和继续
  • 全局同一时间只允许一个音频播放
  • 切换播放时自动暂停当前正在播放的音频
  1. 实现策略

为了满足这些需求,我们采用了以下策略:

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动作允许切换当前活动的音频实例。
  • playpause动作统一管理全局的播放状态,确保状态的一致性。
  1. 实现多音频独立控制的核心机制

a. 实例隔离: 每个useAudioPlayer实例都有自己的audioContext,使得每个音频源可以独立控制。

b. 全局状态同步: 通过Vuex store的currentAudio状态,我们可以跟踪当前活跃的音频实例。

c. 播放切换逻辑: 在play函数中,我们首先检查并暂停当前播放的音频,然后再设置新的当前音频并开始播放。

d. 条件性暂停: pause函数只在当前实例是活跃音频时才执行暂停,这允许其他非活跃实例保持其状态。