@xdfnet/ispeak 1.6.12 → 1.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,6 @@ typedef struct {
21
21
  pthread_cond_t cond;
22
22
  int pending;
23
23
  int closing;
24
- int started;
25
24
  } AVNativePlayer;
26
25
 
27
26
  static char *av_make_error(const char *prefix, NSError *error) {
@@ -91,7 +90,6 @@ static int av_player_create(double sampleRate, unsigned int channels, AVNativePl
91
90
  }
92
91
 
93
92
  [player->node play];
94
- player->started = 1;
95
93
  *out = player;
96
94
  return 0;
97
95
  }
package/main.go CHANGED
@@ -15,7 +15,6 @@ import (
15
15
  "net/http"
16
16
  "os"
17
17
  "os/signal"
18
- "path/filepath"
19
18
  "strings"
20
19
  "sync"
21
20
  "syscall"
@@ -36,9 +35,6 @@ var (
36
35
 
37
36
  var ttsHTTPClient = &http.Client{Timeout: 30 * time.Second}
38
37
 
39
- // 进程级 temp 目录(进程退出时清理)
40
- var tempDir string
41
-
42
38
  var errAlreadyRunning = errors.New("iSpeak already running")
43
39
 
44
40
  type StreamPlayer interface {
@@ -47,81 +43,51 @@ type StreamPlayer interface {
47
43
  Abort() error
48
44
  }
49
45
 
50
- // 单播放器:新的打断旧的,不用队列
46
+ // 最简单的播放器:channel 队列,串行播报
51
47
  type Player struct {
52
- mu sync.Mutex
53
- gen int64
54
- currentGen int64
55
- player StreamPlayer
48
+ ch chan job
49
+ }
50
+
51
+ type job struct {
52
+ text string
53
+ voice VoiceInfo
54
+ cfg Config
56
55
  }
57
56
 
58
57
  func NewPlayer() *Player {
59
- return &Player{}
58
+ p := &Player{ch: make(chan job, 256)}
59
+ go p.loop()
60
+ return p
60
61
  }
61
62
 
62
63
  func (p *Player) Submit(text string, voice VoiceInfo, cfg Config) {
63
- p.mu.Lock()
64
- p.gen++
65
- currentGen := p.gen
66
- p.player = nil
67
-
68
- player, err := newDefaultStreamPlayer()
69
- if err != nil {
70
- log.Printf("启动播放器失败: %v", err)
71
- p.mu.Unlock()
72
- return
73
- }
74
- p.player = player
75
64
  log.Printf("TTS: %s", text)
65
+ p.ch <- job{text, voice, cfg}
66
+ }
76
67
 
77
- startedAt := time.Now()
78
- go func() {
79
- defer func() {
80
- if r := recover(); r != nil {
81
- log.Printf("播报崩溃: %v", r)
82
- }
83
- }()
84
-
85
- onAudio := func(audio []byte) error {
86
- p.mu.Lock()
87
- stale := currentGen != p.gen
88
- p.mu.Unlock()
89
- if stale {
90
- return errors.New("stale")
91
- }
92
- if err := player.Write(audio); err != nil {
93
- return err
94
- }
95
- if len(audio) > 0 {
96
- log.Printf("首个音频 chunk elapsed=%s bytes=%d", time.Since(startedAt).Round(time.Millisecond), len(audio))
97
- }
98
- return nil
68
+ func (p *Player) loop() {
69
+ for j := range p.ch {
70
+ player, err := newDefaultStreamPlayer()
71
+ if err != nil {
72
+ log.Printf("启动播放器失败: %v", err)
73
+ continue
99
74
  }
75
+ p.play(j, player)
76
+ _ = player.CloseAndWait()
77
+ }
78
+ }
100
79
 
101
- if err := synthesizeStream(context.Background(), cfg, text, &voice, onAudio); err != nil {
102
- if !strings.Contains(err.Error(), "stale") {
103
- log.Printf("TTS 合成失败: %v", err)
104
- }
105
- _ = player.Abort()
106
- p.mu.Lock()
107
- if p.player == player {
108
- p.player = nil
109
- }
110
- p.mu.Unlock()
111
- return
112
- }
80
+ func (p *Player) play(j job, player StreamPlayer) {
81
+ startedAt := time.Now()
82
+ onAudio := func(audio []byte) error {
83
+ return player.Write(audio)
84
+ }
113
85
 
114
- log.Printf("TTS 流结束 elapsed=%s", time.Since(startedAt).Round(time.Millisecond))
115
- if err := player.CloseAndWait(); err != nil {
116
- log.Printf("播放器失败: %v", err)
117
- }
118
- p.mu.Lock()
119
- if p.player == player {
120
- p.player = nil
121
- }
122
- p.mu.Unlock()
123
- }()
124
- p.mu.Unlock()
86
+ if err := synthesizeStream(context.Background(), j.cfg, j.text, &j.voice, onAudio); err != nil {
87
+ log.Printf("TTS 合成失败: %v", err)
88
+ return
89
+ }
90
+ log.Printf("TTS: 完成 elapsed=%s", time.Since(startedAt).Round(time.Millisecond))
125
91
  }
126
92
 
127
93
  // 音色信息
@@ -452,15 +418,6 @@ func main() {
452
418
  Compress: true,
453
419
  })
454
420
 
455
- // 创建进程级 temp 目录
456
- cleanupOldTempDirs()
457
- var err error
458
- tempDir, err = os.MkdirTemp("", "ttsd-*")
459
- if err != nil {
460
- log.Fatalf("创建 temp 目录失败: %v", err)
461
- }
462
- defer os.RemoveAll(tempDir)
463
-
464
421
  cfg := loadConfig()
465
422
  if err := validateConfig(cfg); err != nil {
466
423
  log.Fatalf("配置错误: %v", err)
@@ -530,19 +487,6 @@ func listenUnixSocket(socketPath string) (net.Listener, error) {
530
487
  return listener, nil
531
488
  }
532
489
 
533
- // 清理历史遗留的 temp 目录(进程崩溃时留下)
534
- func cleanupOldTempDirs() {
535
- entries, err := os.ReadDir(os.TempDir())
536
- if err != nil {
537
- return
538
- }
539
- for _, e := range entries {
540
- if strings.HasPrefix(e.Name(), "ttsd-") {
541
- os.RemoveAll(filepath.Join(os.TempDir(), e.Name()))
542
- }
543
- }
544
- }
545
-
546
490
  // 校验配置必填项
547
491
  func validateConfig(cfg Config) error {
548
492
  if cfg.APIKey == "" {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdfnet/ispeak",
3
- "version": "1.6.12",
3
+ "version": "1.6.14",
4
4
  "description": "Local macOS TTS daemon for AI coding assistants, powered by Volcengine streaming TTS.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/xdfnet/iSpeak#readme",