Improve heart rate device reconnect flow

This commit is contained in:
2026-03-24 17:17:29 +08:00
parent 71ad6c6535
commit 0ccf7daf50
8 changed files with 831 additions and 72 deletions

View File

@@ -6,14 +6,20 @@ export class SoundDirector {
config: GameAudioConfig
contexts: Partial<Record<AudioCueKey, WechatMiniprogram.InnerAudioContext>>
loopTimers: Partial<Record<AudioCueKey, number>>
backgroundLoopTimer: number
activeGuidanceCue: AudioCueKey | null
backgroundManager: WechatMiniprogram.BackgroundAudioManager | null
appAudioMode: 'foreground' | 'background'
constructor(config: GameAudioConfig = DEFAULT_GAME_AUDIO_CONFIG) {
this.enabled = true
this.config = config
this.contexts = {}
this.loopTimers = {}
this.backgroundLoopTimer = 0
this.activeGuidanceCue = null
this.backgroundManager = null
this.appAudioMode = 'foreground'
}
configure(config: GameAudioConfig): void {
@@ -34,6 +40,7 @@ export class SoundDirector {
}
}
this.loopTimers = {}
this.clearBackgroundLoopTimer()
const keys = Object.keys(this.contexts) as AudioCueKey[]
for (const key of keys) {
@@ -46,6 +53,7 @@ export class SoundDirector {
}
this.contexts = {}
this.activeGuidanceCue = null
this.stopBackgroundGuidance()
}
destroy(): void {
@@ -108,7 +116,43 @@ export class SoundDirector {
}
}
setAppAudioMode(mode: 'foreground' | 'background'): void {
if (this.appAudioMode === mode) {
return
}
this.appAudioMode = mode
const activeGuidanceCue = this.activeGuidanceCue
if (!activeGuidanceCue) {
this.stopBackgroundGuidance()
return
}
if (mode === 'background') {
this.stopForegroundCue(activeGuidanceCue)
this.startBackgroundGuidance(activeGuidanceCue)
return
}
this.stopBackgroundGuidance()
this.playForeground(activeGuidanceCue)
}
play(key: AudioCueKey): void {
if (this.appAudioMode === 'background') {
const cue = this.config.cues[key]
if (!cue || cue.backgroundMode !== 'guidance' || !this.isGuidanceCue(key)) {
return
}
this.startBackgroundGuidance(key)
return
}
this.playForeground(key)
}
playForeground(key: AudioCueKey): void {
const cue = this.config.cues[key]
if (!cue || !cue.src) {
return
@@ -132,11 +176,17 @@ export class SoundDirector {
this.stopGuidanceLoop()
this.activeGuidanceCue = key
this.play(key)
if (this.appAudioMode === 'background') {
this.startBackgroundGuidance(key)
return
}
this.playForeground(key)
}
stopGuidanceLoop(): void {
if (!this.activeGuidanceCue) {
this.stopBackgroundGuidance()
return
}
@@ -148,6 +198,7 @@ export class SoundDirector {
context.seek(0)
}
}
this.stopBackgroundGuidance()
this.activeGuidanceCue = null
}
@@ -175,6 +226,105 @@ export class SoundDirector {
}, cue.loopGapMs) as unknown as number
}
handleBackgroundCueEnded(): void {
const key = this.activeGuidanceCue
if (!key || !this.enabled || !this.config.enabled || this.appAudioMode !== 'background') {
return
}
const cue = this.config.cues[key]
if (!cue || !cue.loop) {
return
}
this.clearBackgroundLoopTimer()
this.backgroundLoopTimer = setTimeout(() => {
this.backgroundLoopTimer = 0
if (this.activeGuidanceCue === key && this.appAudioMode === 'background' && this.enabled && this.config.enabled) {
this.playBackgroundCue(key)
}
}, cue.loopGapMs) as unknown as number
}
clearBackgroundLoopTimer(): void {
if (this.backgroundLoopTimer) {
clearTimeout(this.backgroundLoopTimer)
this.backgroundLoopTimer = 0
}
}
stopForegroundCue(key: AudioCueKey): void {
this.clearLoopTimer(key)
const context = this.contexts[key]
if (!context) {
return
}
context.stop()
if (typeof context.seek === 'function') {
context.seek(0)
}
}
isGuidanceCue(key: AudioCueKey): boolean {
return key === 'guidance:searching'
|| key === 'guidance:approaching'
|| key === 'guidance:ready'
}
startBackgroundGuidance(key: AudioCueKey): void {
if (!this.enabled || !this.config.enabled || !this.config.backgroundAudioEnabled) {
return
}
const cue = this.config.cues[key]
if (!cue || cue.backgroundMode !== 'guidance' || !cue.src) {
return
}
this.playBackgroundCue(key)
}
playBackgroundCue(key: AudioCueKey): void {
const cue = this.config.cues[key]
if (!cue || !cue.src) {
return
}
const manager = this.getBackgroundManager()
this.clearBackgroundLoopTimer()
manager.stop()
manager.title = 'ColorMapRun 引导音'
manager.epname = 'ColorMapRun'
manager.singer = 'ColorMapRun'
manager.coverImgUrl = ''
manager.src = cue.src
manager.play()
}
stopBackgroundGuidance(): void {
this.clearBackgroundLoopTimer()
if (!this.backgroundManager) {
return
}
this.backgroundManager.stop()
}
getBackgroundManager(): WechatMiniprogram.BackgroundAudioManager {
if (this.backgroundManager) {
return this.backgroundManager
}
const manager = wx.getBackgroundAudioManager()
if (typeof manager.onEnded === 'function') {
manager.onEnded(() => {
this.handleBackgroundCueEnded()
})
}
this.backgroundManager = manager
return manager
}
getContext(key: AudioCueKey): WechatMiniprogram.InnerAudioContext {
const existing = this.contexts[key]
if (existing) {