fix(stage-*): incorrect use of useAudioDevices hook

This commit is contained in:
Neko Ayaka
2026-03-26 19:05:54 +08:00
parent 7dbfe9a82c
commit 46147b0f8b
8 changed files with 32 additions and 62 deletions
+3 -1
View File
@@ -7,7 +7,7 @@ import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/c
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
import { useTheme } from '@proj-airi/ui'
import { StageTransitionGroup } from '@proj-airi/ui-transitions'
import { storeToRefs } from 'pinia'
@@ -26,6 +26,7 @@ const settings = storeToRefs(settingsStore)
const onboardingStore = useOnboardingStore()
const serverChannelStore = useModsServerChannelStore()
const characterOrchestratorStore = useCharacterOrchestratorStore()
const settingsAudioDeviceStore = useSettingsAudioDevice()
const { showingSetup } = storeToRefs(onboardingStore)
const { isDark } = useTheme()
const cardStore = useAiriCardStore()
@@ -80,6 +81,7 @@ onMounted(async () => {
await displayModelsStore.loadDisplayModelsFromIndexedDB()
await settingsStore.initializeStageModel()
await settingsAudioDeviceStore.initialize()
})
onUnmounted(() => {
+3 -1
View File
@@ -14,7 +14,7 @@ import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/conte
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { usePerfTracerBridgeStore } from '@proj-airi/stage-ui/stores/perf-tracer-bridge'
import { listProvidersForPluginHost, shouldPublishPluginHostCapabilities } from '@proj-airi/stage-ui/stores/plugin-host-capabilities'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
import { useTheme } from '@proj-airi/ui'
import { storeToRefs } from 'pinia'
import { onMounted, onUnmounted, watch } from 'vue'
@@ -61,6 +61,7 @@ const characterOrchestratorStore = useCharacterOrchestratorStore()
const analyticsStore = useSharedAnalyticsStore()
const pluginHostInspectorStore = usePluginHostInspectorStore()
const stageWindowLifecycleStore = useStageWindowLifecycleStore()
const settingsAudioDeviceStore = useSettingsAudioDevice()
const context = useElectronEventaContext()
usePerfTracerBridgeStore()
initializeStageThreeRuntimeTraceBridge()
@@ -123,6 +124,7 @@ onMounted(async () => {
await chatSessionStore.initialize()
await displayModelsStore.loadDisplayModelsFromIndexedDB()
await settingsStore.initializeStageModel()
await settingsAudioDeviceStore.initialize()
const serverChannelConfig = await getServerChannelConfig()
serverChannelSettingsStore.websocketTlsConfig = serverChannelConfig.tlsConfig
+3 -1
View File
@@ -8,7 +8,7 @@ import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/c
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
import { useTheme } from '@proj-airi/ui'
import { StageTransitionGroup } from '@proj-airi/ui-transitions'
import { storeToRefs } from 'pinia'
@@ -32,6 +32,7 @@ const onboardingStore = useOnboardingStore()
const chatSessionStore = useChatSessionStore()
const serverChannelStore = useModsServerChannelStore()
const characterOrchestratorStore = useCharacterOrchestratorStore()
const settingsAudioDeviceStore = useSettingsAudioDevice()
const { showingSetup } = storeToRefs(onboardingStore)
const { isDark } = useTheme()
const cardStore = useAiriCardStore()
@@ -93,6 +94,7 @@ onMounted(async () => {
await displayModelsStore.loadDisplayModelsFromIndexedDB()
await settingsStore.initializeStageModel()
await settingsAudioDeviceStore.initialize()
})
onUnmounted(() => {
@@ -6,9 +6,9 @@ import { until } from '@vueuse/core'
import { computed, onUnmounted, ref, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAudioDevice } from '../../../composables/audio'
import { useAudioAnalyzer } from '../../../composables/audio/audio-analyzer'
import { useAudioRecorder } from '../../../composables/audio/audio-recorder'
import { useAudioDevice } from '../../../composables/audio/device'
import { LevelMeter, TestDummyMarker, ThresholdMeter } from '../../gadgets'
const props = defineProps<{
@@ -1,13 +1,13 @@
import { useDevicesList, useUserMedia } from '@vueuse/core'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { computed, nextTick, ref, watch } from 'vue'
export function useAudioDevice() {
const devices = useDevicesList({ constraints: { audio: true }, requestPermissions: true })
export function useAudioDevice(requestPermission: boolean = false) {
const devices = useDevicesList({ constraints: { audio: true }, requestPermissions: requestPermission })
const audioInputs = computed(() => devices.audioInputs.value)
const selectedAudioInput = ref<string>(devices.audioInputs.value[0]?.deviceId || '')
const selectedAudioInput = ref<string>(devices.audioInputs.value.find(device => device.deviceId === 'default')?.deviceId || '')
const deviceConstraints = computed<MediaStreamConstraints>(() => ({
audio: {
...(selectedAudioInput.value ? { deviceId: { exact: selectedAudioInput.value } } : {}),
deviceId: { exact: selectedAudioInput.value },
autoGainControl: true,
echoCancellation: true,
noiseSuppression: true,
@@ -17,13 +17,12 @@ export function useAudioDevice() {
watch(audioInputs, () => {
if (!selectedAudioInput.value && audioInputs.value.length > 0) {
selectedAudioInput.value = audioInputs.value[0]?.deviceId
selectedAudioInput.value = audioInputs.value.find(input => input.deviceId === 'default')?.deviceId || audioInputs.value[0].deviceId
}
})
// Lifecycle
onMounted(() => {
devices.ensurePermissions()
function askPermission() {
return devices.ensurePermissions()
.then(() => nextTick())
.then(() => {
if (audioInputs.value.length > 0 && !selectedAudioInput.value) {
@@ -32,15 +31,18 @@ export function useAudioDevice() {
})
.catch((error) => {
console.error('Error ensuring permissions:', error)
throw error // Re-throw so callers can handle the error
})
})
}
return {
audioInputs,
selectedAudioInput,
stream,
stopStream,
startStream,
deviceConstraints,
askPermission,
startStream,
stopStream,
}
}
@@ -1,4 +1,4 @@
export * from './audio-analyzer'
export * from './audio-context'
export * from './audio-device'
export * from './audio-recorder'
export * from './device'
+1 -41
View File
@@ -1,6 +1,5 @@
import { useDevicesList, useUserMedia } from '@vueuse/core'
import { defineStore } from 'pinia'
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
import { computed, ref, shallowRef } from 'vue'
function calculateVolumeWithLinearNormalize(analyser: AnalyserNode) {
const dataBuffer = new Uint8Array(analyser.frequencyBinCount)
@@ -74,45 +73,6 @@ export const useAudioContext = defineStore('audio-context', () => {
}
})
export function useAudioDevice(requestPermission: boolean = false) {
const devices = useDevicesList({ constraints: { audio: true }, requestPermissions: requestPermission })
const audioInputs = computed(() => devices.audioInputs.value)
const selectedAudioInput = ref<string>(devices.audioInputs.value.find(device => device.deviceId === 'default')?.deviceId || '')
const deviceConstraints = computed<MediaStreamConstraints>(() => ({ audio: { deviceId: { exact: selectedAudioInput.value }, autoGainControl: true, echoCancellation: true, noiseSuppression: true } }))
const { stream, stop: stopStream, start: startStream } = useUserMedia({ constraints: deviceConstraints, enabled: false, autoSwitch: true })
watch(audioInputs, () => {
if (!selectedAudioInput.value && audioInputs.value.length > 0) {
selectedAudioInput.value = audioInputs.value.find(input => input.deviceId === 'default')?.deviceId || audioInputs.value[0].deviceId
}
})
function askPermission() {
return devices.ensurePermissions()
.then(() => nextTick())
.then(() => {
if (audioInputs.value.length > 0 && !selectedAudioInput.value) {
selectedAudioInput.value = audioInputs.value.find(input => input.deviceId === 'default')?.deviceId || audioInputs.value[0].deviceId
}
})
.catch((error) => {
console.error('Error ensuring permissions:', error)
throw error // Re-throw so callers can handle the error
})
}
return {
audioInputs,
selectedAudioInput,
stream,
deviceConstraints,
askPermission,
startStream,
stopStream,
}
}
export const useSpeakingStore = defineStore('character-speaking', () => {
const nowSpeakingAvatarBorderOpacityMin = 30
const nowSpeakingAvatarBorderOpacityMax = 100
@@ -1,8 +1,8 @@
import { useLocalStorageManualReset } from '@proj-airi/stage-shared/composables'
import { defineStore } from 'pinia'
import { onMounted, watch } from 'vue'
import { watch } from 'vue'
import { useAudioDevice } from '../audio'
import { useAudioDevice } from '../../composables/audio'
export const useSettingsAudioDevice = defineStore('settings-audio-devices', () => {
const { audioInputs, deviceConstraints, selectedAudioInput: selectedAudioInputNonPersist, startStream, stopStream, stream, askPermission } = useAudioDevice()
@@ -23,7 +23,7 @@ export const useSettingsAudioDevice = defineStore('settings-audio-devices', () =
}
})
onMounted(() => {
function initialize() {
const hasSelectedInput = selectedAudioInputPersist.value
&& audioInputs.value.some(device => device.deviceId === selectedAudioInputPersist.value)
@@ -33,7 +33,7 @@ export const useSettingsAudioDevice = defineStore('settings-audio-devices', () =
if (selectedAudioInputNonPersist.value && !selectedAudioInputEnabledPersist.value) {
selectedAudioInputPersist.value = selectedAudioInputNonPersist.value
}
})
}
function resetState() {
selectedAudioInputPersist.reset()
@@ -50,6 +50,8 @@ export const useSettingsAudioDevice = defineStore('settings-audio-devices', () =
stream,
initialize,
askPermission,
startStream,
stopStream,