package voice import ( "context" "fmt" "sync" ) // Service 在后台工作池上运行 STT/TTS 请求,使调用方 // goroutine(智能体循环)绝不会被提供方延迟阻塞。每个 // 请求入队到 jobs channel,并在每个 job 自己的结果 // channel 上应答;调用方 context 被取消或服务已关闭时, // 调用方立刻解除阻塞,且不泄漏任何 worker goroutine。它可安全并发使用。 type Service struct { chain *Chain jobs chan job bgCtx context.Context cancel context.CancelFunc nWorkers int wg sync.WaitGroup mu sync.RWMutex closed bool } // ServiceOption 定制 Service。 type ServiceOption func(*Service) // WithWorkers 设置工作池大小。小于等于 0 的值被忽略, // 保留默认的单 worker。 func WithWorkers(n int) ServiceOption { return func(s *Service) { if n > 0 { s.nWorkers = n } } } // jobKind 选择 worker 执行的操作。 type jobKind int const ( jobSTT jobKind = iota jobTTS ) // job 是调用方入队的一个请求。 type job struct { kind jobKind audio Audio text string res chan jobResult } // jobResult 承载写回调用方的结果。 type jobResult struct { text string audio Audio provider string err error } // NewService 构造一个由 chain 支撑的 Service,并启动工作池。 func NewService(chain *Chain, opts ...ServiceOption) *Service { bg, cancel := context.WithCancel(context.Background()) s := &Service{ chain: chain, jobs: make(chan job), bgCtx: bg, cancel: cancel, nWorkers: 1, } for _, o := range opts { o(s) } for i := 0; i < s.nWorkers; i++ { s.wg.Add(1) go s.worker() } return s } // worker 消费 job 直到服务关闭。 func (s *Service) worker() { defer s.wg.Done() for { select { case <-s.bgCtx.Done(): return case j := <-s.jobs: s.run(j) } } } // run 执行一个 job 并投递结果。提供方在服务的 // 后台 context 下运行,因此 Close 会取消在途工作,且不泄漏 // worker goroutine;结果写入带缓冲的 channel,因此已 // 返回的调用方绝不会阻塞该 worker。 func (s *Service) run(j job) { res := jobResult{} switch j.kind { case jobSTT: res.text, res.provider, res.err = s.chain.STTWithProvider(s.bgCtx, j.audio) case jobTTS: res.audio, res.provider, res.err = s.chain.TTSWithProvider(s.bgCtx, j.text) } j.res <- res } // STT 入队一个转写请求并等待结果。ctx 会取消 // 等待以及底层提供方调用。 func (s *Service) STT(ctx context.Context, audio Audio) (string, error) { text, _, err := s.STTWithProvider(ctx, audio) return text, err } // STTWithProvider 即 STT,但额外报告产出该转写的 // 提供方级别名。 func (s *Service) STTWithProvider(ctx context.Context, audio Audio) (string, string, error) { res, err := s.submit(ctx, job{kind: jobSTT, audio: audio, res: make(chan jobResult, 1)}) if err != nil { return "", "", err } return res.text, res.provider, res.err } // TTS 入队一个合成请求并等待结果。ctx 会取消 // 等待以及底层提供方调用。 func (s *Service) TTS(ctx context.Context, text string) (Audio, error) { audio, _, err := s.TTSWithProvider(ctx, text) return audio, err } // TTSWithProvider 即 TTS,但额外报告产出该音频的 // 提供方级别名。 func (s *Service) TTSWithProvider(ctx context.Context, text string) (Audio, string, error) { res, err := s.submit(ctx, job{kind: jobTTS, text: text, res: make(chan jobResult, 1)}) if err != nil { return Audio{}, "", err } return res.audio, res.provider, res.err } // CloneVoice 创建自定义音色,交由底层链处理。它是一次性 // 管理操作(不是工作池 job),因此直接运行。 func (s *Service) CloneVoice(ctx context.Context, referenceURL, prefix string) (string, error) { return s.chain.CloneVoice(ctx, referenceURL, prefix) } // submit 把 j 入队并阻塞,直到拿到结果或发生取消。 func (s *Service) submit(ctx context.Context, j job) (jobResult, error) { select { case s.jobs <- j: case <-ctx.Done(): return jobResult{}, fmt.Errorf("voice: %w", ctx.Err()) case <-s.bgCtx.Done(): return jobResult{}, ErrClosed } select { case r := <-j.res: return r, nil case <-ctx.Done(): return jobResult{}, fmt.Errorf("voice: %w", ctx.Err()) case <-s.bgCtx.Done(): return jobResult{}, ErrClosed } } // Close 取消工作池并等待在途 job 结束。调用一次是 // 安全的;后续调用为空操作。在途提供方调用由各提供方 // 自己的 context 超时兜底,因此 Close 会迅速返回。 func (s *Service) Close() { s.mu.Lock() defer s.mu.Unlock() if s.closed { return } s.closed = true s.cancel() s.wg.Wait() }