@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.
- package/avaudioengine_player_darwin.go +0 -2
- package/main.go +33 -89
- package/package.json +1 -1
|
@@ -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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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