agentvibes 5.7.5 → 5.7.6
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/.agentvibes/install-manifest.json +115 -111
- package/.claude/config/audio-effects.cfg +1 -1
- package/.claude/config/background-music-position.txt +1 -1
- package/.claude/github-star-reminder.txt +1 -1
- package/.claude/hooks/play-tts-ssh-remote.sh +36 -10
- package/README.md +9 -9
- package/RELEASE_NOTES.md +31 -0
- package/mcp-server/WINDOWS_SETUP.md +1 -1
- package/mcp-server/docs/troubleshooting-audio.md +1 -1
- package/package.json +1 -1
- package/src/console/audio-env.js +19 -0
- package/src/console/tabs/settings-tab.js +74 -0
- package/src/console/tabs/voices-tab.js +46 -28
- package/templates/agentvibes-receiver.sh +139 -66
|
@@ -151,12 +151,23 @@ SOX_EFFECTS=""
|
|
|
151
151
|
BG_FILE=""
|
|
152
152
|
BG_VOLUME="0.10"
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
# Use CLAUDE_PROJECT_DIR (injected via --project-dir by play-tts.sh) so the
|
|
155
|
+
# agent-name / default row is read from the user's project, not the package.
|
|
156
|
+
if [[ -n "${CLAUDE_PROJECT_DIR:-}" && -d "$CLAUDE_PROJECT_DIR/.claude" ]]; then
|
|
157
|
+
EFFECTS_CFG="$CLAUDE_PROJECT_DIR/.claude/config/audio-effects.cfg"
|
|
158
|
+
else
|
|
159
|
+
EFFECTS_CFG="$PROJECT_ROOT/.claude/config/audio-effects.cfg"
|
|
160
|
+
fi
|
|
155
161
|
if [[ -f "$EFFECTS_CFG" ]]; then
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
# awk exact field-1 match — no regex injection risk from AGENT_NAME
|
|
163
|
+
CONFIG_LINE=$(awk -F'|' -v k="${AGENT_NAME}" '$1==k{print;exit}' "$EFFECTS_CFG" 2>/dev/null || true)
|
|
164
|
+
[[ -z "$CONFIG_LINE" ]] && \
|
|
165
|
+
CONFIG_LINE=$(awk -F'|' '$1=="default"{print;exit}' "$EFFECTS_CFG" 2>/dev/null || true)
|
|
158
166
|
if [[ -n "$CONFIG_LINE" ]]; then
|
|
159
167
|
IFS='|' read -r _ SOX_EFFECTS BG_FILE BG_VOLUME <<< "$CONFIG_LINE"
|
|
168
|
+
SOX_EFFECTS="${SOX_EFFECTS## }"; SOX_EFFECTS="${SOX_EFFECTS%% }"
|
|
169
|
+
BG_FILE="${BG_FILE## }"; BG_FILE="${BG_FILE%% }"
|
|
170
|
+
BG_VOLUME="${BG_VOLUME## }"; BG_VOLUME="${BG_VOLUME%% }"
|
|
160
171
|
fi
|
|
161
172
|
fi
|
|
162
173
|
|
|
@@ -172,14 +183,18 @@ LLM_REVERB=""
|
|
|
172
183
|
LLM_BG_FILE=""
|
|
173
184
|
LLM_BG_VOLUME=""
|
|
174
185
|
|
|
175
|
-
# Build config search path
|
|
176
|
-
#
|
|
177
|
-
#
|
|
186
|
+
# Build config search path for LLM-specific row lookup.
|
|
187
|
+
# Priority: CLAUDE_PROJECT_DIR (real user project) → global HOME fallback.
|
|
188
|
+
# PROJECT_ROOT is intentionally excluded when CLAUDE_PROJECT_DIR is set and
|
|
189
|
+
# different — prevents the AgentVibes package's own audio-effects.cfg from
|
|
190
|
+
# bleeding into a user project that doesn't define its own llm: row.
|
|
178
191
|
_llm_cfg_paths=()
|
|
179
192
|
if [[ -n "${CLAUDE_PROJECT_DIR:-}" && "$CLAUDE_PROJECT_DIR" != "$PROJECT_ROOT" ]]; then
|
|
180
193
|
_llm_cfg_paths+=("$CLAUDE_PROJECT_DIR/.claude/config/audio-effects.cfg")
|
|
194
|
+
else
|
|
195
|
+
_llm_cfg_paths+=("$PROJECT_ROOT/.claude/config/audio-effects.cfg")
|
|
181
196
|
fi
|
|
182
|
-
_llm_cfg_paths+=("$
|
|
197
|
+
_llm_cfg_paths+=("$HOME/.claude/config/audio-effects.cfg")
|
|
183
198
|
|
|
184
199
|
_llm_key="llm:${LLM_NAME}"
|
|
185
200
|
_llm_row_found=0
|
|
@@ -224,7 +239,11 @@ if [[ -n "$AGENT_PROFILE_FILE" ]] && [[ -f "$AGENT_PROFILE_FILE" ]]; then
|
|
|
224
239
|
fi
|
|
225
240
|
fi
|
|
226
241
|
|
|
227
|
-
#
|
|
242
|
+
# PRETEXT is NOT extracted from the llm row here.
|
|
243
|
+
# play-tts.sh already prepends the llm row's pretext to TEXT before calling this script.
|
|
244
|
+
# Extracting it again and sending it as a separate JSON field would cause the receiver
|
|
245
|
+
# to prepend it a second time — the user hears the intro text twice.
|
|
246
|
+
# PRETEXT here is only for the (rare) pretext.txt override file, not the llm row.
|
|
228
247
|
PRETEXT=""
|
|
229
248
|
PRETEXT_FILE="$PROJECT_ROOT/.agentvibes/config/pretext.txt"
|
|
230
249
|
if [[ -f "$PRETEXT_FILE" ]]; then
|
|
@@ -279,9 +298,9 @@ build_json_payload() {
|
|
|
279
298
|
--arg llm "$LLM_NAME" \
|
|
280
299
|
'{text: $text, voice: $voice, effects: $effects, music: $music, volume: $volume, project: $project, pretext: $pretext, speed: $speed, provider: $provider, llm: $llm}'
|
|
281
300
|
else
|
|
282
|
-
# Manual JSON — escape
|
|
301
|
+
# Manual JSON — escape backslashes, quotes, control chars
|
|
283
302
|
local escaped_text
|
|
284
|
-
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g')
|
|
303
|
+
escaped_text=$(printf '%s' "$TEXT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' ' | sed 's/\r//g')
|
|
285
304
|
local escaped_pretext
|
|
286
305
|
escaped_pretext=$(printf '%s' "$PRETEXT" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
287
306
|
printf '{"text":"%s","voice":"%s","effects":"%s","music":"%s","volume":"%s","project":"%s","pretext":"%s","speed":"%s","provider":"%s","llm":"%s"}' \
|
|
@@ -303,6 +322,13 @@ fi
|
|
|
303
322
|
# Send to receiver via SSH (fire and forget — backgrounded)
|
|
304
323
|
# ---------------------------------------------------------------------------
|
|
305
324
|
|
|
325
|
+
# In test mode, dump the decoded payload to stdout so tests can inspect it
|
|
326
|
+
# without needing a real SSH connection or a mock binary.
|
|
327
|
+
if [[ "${AGENTVIBES_TEST_MODE:-false}" == "true" ]]; then
|
|
328
|
+
echo "$JSON_PAYLOAD"
|
|
329
|
+
exit 0
|
|
330
|
+
fi
|
|
331
|
+
|
|
306
332
|
echo "Sending to $SSH_HOST..." >&2
|
|
307
333
|
|
|
308
334
|
# Build SSH args — use explicit key/port from config if available, else rely on ~/.ssh/config
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
[](https://github.com/paulpreibisch/AgentVibes/actions/workflows/publish.yml)
|
|
12
12
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
13
13
|
|
|
14
|
-
**Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.7.
|
|
14
|
+
**Author**: Paul Preibisch ([@997Fire](https://x.com/997Fire)) | **Version**: v5.7.6
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -40,19 +40,19 @@ Whether you're coding in Claude Code, chatting in Claude Desktop, using Warp Ter
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
## 🌟 NEW IN v5.7.
|
|
43
|
+
## 🌟 NEW IN v5.7.6 — SSH Remote Payload Integrity + Receiver Rewrite
|
|
44
44
|
|
|
45
|
-
**
|
|
45
|
+
**SSH remote music/voice fix:** The correct project music track and voice now reach the remote receiver — previously the global config was used instead of the active project's settings.
|
|
46
46
|
|
|
47
|
-
**
|
|
47
|
+
**Bash receiver rewrite:** The Linux/Termux `agentvibes-receiver.sh` has been fully rewritten to decode the current base64 JSON payload format. The old positional-arg format from pre-v5.5 is gone.
|
|
48
48
|
|
|
49
|
-
**
|
|
49
|
+
**No more double intro:** The personality pretext (e.g., "Bcs latin dance here") is no longer spoken twice over SSH remote. `play-tts.sh` prepends it to the text; the receiver no longer gets a separate pretext field to prepend again.
|
|
50
50
|
|
|
51
|
-
**
|
|
51
|
+
**SSH host visible in TUI:** The Settings and Voices tabs now display your configured SSH remote host alias.
|
|
52
52
|
|
|
53
|
-
**
|
|
53
|
+
**Security fixes** and 24 new BATS tests covering the full sender → receiver round-trip.
|
|
54
54
|
|
|
55
|
-
## v5.7.
|
|
55
|
+
## v5.7.5 — TUI Button Contrast + BMAD Routing Fixes
|
|
56
56
|
|
|
57
57
|
**BMAD v6.6.0:** AgentVibes now detects the new `.claude/skills/*/agents/` agent structure, correctly handles globally-installed BMAD at `~/_bmad`, and gracefully skips v6.6+ plain-Markdown agents during TTS injection instead of erroring. The BMAD tab now shows detection correctly for global installs.
|
|
58
58
|
|
|
@@ -353,7 +353,7 @@ All 50+ Piper voices AgentVibes provides are sourced from Hugging Face's open-so
|
|
|
353
353
|
|
|
354
354
|
## 📰 Latest Release
|
|
355
355
|
|
|
356
|
-
**[v3.6.0 - "Voice Explorer" Release](https://github.com/paulpreibisch/AgentVibes/releases/tag/
|
|
356
|
+
**[v3.6.0 - "Voice Explorer" Release](https://github.com/paulpreibisch/AgentVibes/releases/tag/v5.7.5)** 🎉
|
|
357
357
|
|
|
358
358
|
### 🎤 Voices Tab — Browse & Sample 914 Voices
|
|
359
359
|
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# AgentVibes Release Notes
|
|
2
2
|
|
|
3
|
+
## 🔧 v5.7.6 — SSH Remote Payload Integrity + Receiver Rewrite
|
|
4
|
+
|
|
5
|
+
**Released:** 2026-05-16
|
|
6
|
+
|
|
7
|
+
### 🐛 SSH Remote Playing Wrong Music and Voice
|
|
8
|
+
|
|
9
|
+
When using the SSH remote TTS feature, the wrong project's music track and voice were being applied. Root cause: `CLAUDE_PROJECT_DIR` was not forwarded to the sender, causing it to fall back to the global config instead of the active project's `audio-effects.cfg`.
|
|
10
|
+
|
|
11
|
+
### 🐛 Bash Receiver Incompatible with JSON Payload Format
|
|
12
|
+
|
|
13
|
+
The Linux/Termux bash receiver (`agentvibes-receiver.sh`) was using a positional-argument format from pre-v5.5 and could not decode the current base64 JSON payload at all. The receiver has been fully rewritten to match the PowerShell receiver's logic: decodes base64, parses JSON, applies voice/music/effects/volume, and validates all fields.
|
|
14
|
+
|
|
15
|
+
### 🐛 Personality Intro Heard Twice on Remote
|
|
16
|
+
|
|
17
|
+
The personality pretext (e.g., "Bcs latin dance here") was being spoken twice when using SSH remote TTS. Root cause: `play-tts.sh` already prepends the pretext to the speech text before calling the sender; the sender was also packing it into the JSON `pretext` field, causing the receiver to prepend it again. The `pretext` JSON field is now intentionally left empty — the personality is delivered via the `text` field only.
|
|
18
|
+
|
|
19
|
+
### 🆕 SSH Host Alias Visible in Settings Tab
|
|
20
|
+
|
|
21
|
+
The configured SSH remote host alias is now displayed in the Settings and Voices tabs so users can confirm which remote machine TTS is targeting without opening config files.
|
|
22
|
+
|
|
23
|
+
### 🔒 Security Fixes
|
|
24
|
+
|
|
25
|
+
Input validation improvements in the SSH remote sender and receiver.
|
|
26
|
+
|
|
27
|
+
### 🧪 24 New BATS Tests
|
|
28
|
+
|
|
29
|
+
- 15 SSH remote payload tests: verify voice, music track, volume, reverb/effects, pretext handling, LLM identifier, project config precedence, and JSON validity
|
|
30
|
+
- 9 end-to-end round-trip tests: sender builds payload → receiver decodes and applies all fields simultaneously, catching regressions in either end
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
3
34
|
## 🖥️ v5.7.5 — TUI Button Contrast + BMAD Routing Fixes
|
|
4
35
|
|
|
5
36
|
**Released:** 2026-05-13
|
|
@@ -121,7 +121,7 @@ Now we'll tell Claude Desktop to use AgentVibes.
|
|
|
121
121
|
"mcpServers": {
|
|
122
122
|
"agentvibes": {
|
|
123
123
|
"command": "npx",
|
|
124
|
-
"args": ["-y", "agentvibes@beta", "agentvibes-mcp-server"]
|
|
124
|
+
"args": ["-y", "-p", "agentvibes@beta", "agentvibes-mcp-server"]
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "agentvibes",
|
|
4
|
-
"version": "5.7.
|
|
4
|
+
"version": "5.7.6",
|
|
5
5
|
"description": "Now your AI Agents can finally talk back! Professional TTS voice for Claude Code, Claude Desktop (via MCP), and Clawdbot with multi-provider support.",
|
|
6
6
|
"homepage": "https://agentvibes.org",
|
|
7
7
|
"keywords": [
|
package/src/console/audio-env.js
CHANGED
|
@@ -163,3 +163,22 @@ export function detectWavPlayer(env) {
|
|
|
163
163
|
}
|
|
164
164
|
return _detect(WAV_PLAYERS, env);
|
|
165
165
|
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Returns all installed WAV players in preference order.
|
|
169
|
+
* Used for fallback playback — try each until one succeeds.
|
|
170
|
+
* On Windows, always appends the PowerShell fallback.
|
|
171
|
+
*
|
|
172
|
+
* @param {Object} [env] - Environment (defaults to buildAudioEnv())
|
|
173
|
+
* @returns {Player[]}
|
|
174
|
+
*/
|
|
175
|
+
export function getAllWavPlayers(env) {
|
|
176
|
+
env = env || buildAudioEnv();
|
|
177
|
+
const whichCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
178
|
+
const installed = WAV_PLAYERS.filter(p => {
|
|
179
|
+
const r = spawnSync(whichCmd, [p.bin], { stdio: 'pipe', env });
|
|
180
|
+
return r.status === 0;
|
|
181
|
+
});
|
|
182
|
+
if (process.platform === 'win32') installed.push(WIN_WAV_PLAYER);
|
|
183
|
+
return installed;
|
|
184
|
+
}
|
|
@@ -201,6 +201,15 @@ export function createSettingsTab(screen, services) {
|
|
|
201
201
|
},
|
|
202
202
|
desc: 'Play audio locally or stream to a remote host via SSH',
|
|
203
203
|
},
|
|
204
|
+
{
|
|
205
|
+
key: 'sshAlias',
|
|
206
|
+
label: 'SSH Host Alias',
|
|
207
|
+
getValue: () => {
|
|
208
|
+
const alias = configService?.getConfig?.()?.audio_ssh_alias ?? '';
|
|
209
|
+
return alias || '(not set)';
|
|
210
|
+
},
|
|
211
|
+
desc: 'SSH host alias from ~/.ssh/config used when Audio Destination is Remote',
|
|
212
|
+
},
|
|
204
213
|
{
|
|
205
214
|
key: 'configStorage',
|
|
206
215
|
label: 'Config Storage',
|
|
@@ -395,6 +404,9 @@ export function createSettingsTab(screen, services) {
|
|
|
395
404
|
case 'audioDst':
|
|
396
405
|
_editAudioDst();
|
|
397
406
|
break;
|
|
407
|
+
case 'sshAlias':
|
|
408
|
+
_editSshAlias();
|
|
409
|
+
break;
|
|
398
410
|
case 'rerunWizard':
|
|
399
411
|
_rerunWizard();
|
|
400
412
|
break;
|
|
@@ -864,6 +876,68 @@ export function createSettingsTab(screen, services) {
|
|
|
864
876
|
screen.render();
|
|
865
877
|
}
|
|
866
878
|
|
|
879
|
+
// ── SSH alias editor ─────────────────────────────────────────────────────
|
|
880
|
+
|
|
881
|
+
function _editSshAlias() {
|
|
882
|
+
navigationService?.openModal();
|
|
883
|
+
|
|
884
|
+
const aliases = _detectSshAliases().filter(a => !a.includes('github.com'));
|
|
885
|
+
const MANUAL_OPTION = ' Type manually...';
|
|
886
|
+
const items = [...aliases.map(a => ` ${a}`), MANUAL_OPTION];
|
|
887
|
+
const currentAlias = configService?.getConfig?.()?.audio_ssh_alias ?? '';
|
|
888
|
+
|
|
889
|
+
const listHeight = Math.min(items.length + 4, 18);
|
|
890
|
+
const modal = blessed.list({
|
|
891
|
+
parent: screen,
|
|
892
|
+
top: 'center',
|
|
893
|
+
left: 'center',
|
|
894
|
+
width: 44,
|
|
895
|
+
height: listHeight,
|
|
896
|
+
border: { type: 'line' },
|
|
897
|
+
tags: true,
|
|
898
|
+
label: ' {bold}{cyan-fg} Select SSH Host Alias {/cyan-fg}{/bold} ',
|
|
899
|
+
keys: true,
|
|
900
|
+
vi: true,
|
|
901
|
+
mouse: true,
|
|
902
|
+
style: {
|
|
903
|
+
fg: COLORS.labelFg,
|
|
904
|
+
bg: COLORS.contentBg,
|
|
905
|
+
border: { fg: 'cyan' },
|
|
906
|
+
selected: { bg: 'blue', fg: 'yellow' },
|
|
907
|
+
item: { fg: COLORS.labelFg },
|
|
908
|
+
},
|
|
909
|
+
});
|
|
910
|
+
modal.setFront();
|
|
911
|
+
modal.setItems(items);
|
|
912
|
+
|
|
913
|
+
const currentIdx = aliases.indexOf(currentAlias);
|
|
914
|
+
if (currentIdx >= 0) modal.select(currentIdx);
|
|
915
|
+
|
|
916
|
+
modal.key(['enter'], () => {
|
|
917
|
+
const selItem = items[modal.selected];
|
|
918
|
+
if (selItem === MANUAL_OPTION) {
|
|
919
|
+
_closeModal();
|
|
920
|
+
_promptSshAlias();
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
const alias = selItem?.trim();
|
|
924
|
+
if (alias) configService.set('audio_ssh_alias', alias);
|
|
925
|
+
_closeModal();
|
|
926
|
+
});
|
|
927
|
+
modal.key(['escape', 'q'], _closeModal);
|
|
928
|
+
|
|
929
|
+
function _closeModal() {
|
|
930
|
+
navigationService?.closeModal();
|
|
931
|
+
destroyList(modal, screen);
|
|
932
|
+
_refreshValues();
|
|
933
|
+
box.focus();
|
|
934
|
+
screen.render();
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
modal.focus();
|
|
938
|
+
screen.render();
|
|
939
|
+
}
|
|
940
|
+
|
|
867
941
|
// ── Re-run wizard ────────────────────────────────────────────────────────
|
|
868
942
|
|
|
869
943
|
function _rerunWizard() {
|
|
@@ -13,7 +13,7 @@ import path from 'node:path';
|
|
|
13
13
|
import os from 'node:os';
|
|
14
14
|
import { spawn } from 'node:child_process';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { buildAudioEnv, detectWavPlayer } from '../audio-env.js';
|
|
16
|
+
import { buildAudioEnv, detectWavPlayer, getAllWavPlayers } from '../audio-env.js';
|
|
17
17
|
import { SURNAME_POOL, uniquifyVoiceName } from '../../utils/voice-names.js';
|
|
18
18
|
|
|
19
19
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -1022,9 +1022,9 @@ export function createVoicesTab(screen, services) {
|
|
|
1022
1022
|
return;
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
|
-
// Play the synthesized wav
|
|
1026
|
-
const
|
|
1027
|
-
if (
|
|
1025
|
+
// Play the synthesized wav — try each installed player until one succeeds
|
|
1026
|
+
const _wavPlayers = getAllWavPlayers(_spawnEnv);
|
|
1027
|
+
if (_wavPlayers.length === 0) {
|
|
1028
1028
|
_playingVoiceId = null;
|
|
1029
1029
|
_playingProcess = null;
|
|
1030
1030
|
previewLine.setContent(`{red-fg}No audio player found. Install ffmpeg.{/red-fg}`);
|
|
@@ -1033,44 +1033,62 @@ export function createVoicesTab(screen, services) {
|
|
|
1033
1033
|
try { fs.unlinkSync(tempWav); } catch {}
|
|
1034
1034
|
return;
|
|
1035
1035
|
}
|
|
1036
|
-
|
|
1037
|
-
stdio: 'ignore',
|
|
1038
|
-
detached: !isWindows,
|
|
1039
|
-
windowsHide: true,
|
|
1040
|
-
env: _spawnEnv,
|
|
1041
|
-
});
|
|
1036
|
+
|
|
1042
1037
|
// Race note: _playingVoiceId could change between piper exit and here
|
|
1043
1038
|
// if the user stops playback. Re-check before assigning to avoid orphan.
|
|
1044
1039
|
if (_playingVoiceId !== voiceId) { try { fs.unlinkSync(tempWav); } catch {} return; }
|
|
1045
|
-
_playingProcess = playProc;
|
|
1046
1040
|
|
|
1047
1041
|
previewLine.setContent(`{${COLORS.activeFg}-fg}♪ Playing: ${voiceId} (Enter/Space to stop){/${COLORS.activeFg}-fg}`);
|
|
1048
1042
|
screen.render();
|
|
1049
1043
|
|
|
1050
|
-
|
|
1051
|
-
if (
|
|
1044
|
+
function _tryNextPlayer(remainingPlayers) {
|
|
1045
|
+
if (!remainingPlayers.length) {
|
|
1052
1046
|
_playingVoiceId = null;
|
|
1053
1047
|
_playingProcess = null;
|
|
1048
|
+
previewLine.setContent(`{red-fg}♪ Audio playback failed (no audio device?) — check your provider in Setup{/red-fg}`);
|
|
1049
|
+
screen.render();
|
|
1050
|
+
setTimeout(() => { if (!_closed) { previewLine.setContent(_listFocused ? HINT_TEXT : ''); screen.render(); } }, 5000);
|
|
1051
|
+
try { fs.unlinkSync(tempWav); } catch {}
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const [wavP, ...rest] = remainingPlayers;
|
|
1055
|
+
const playProc = spawn(wavP.bin, wavP.args(tempWav), {
|
|
1056
|
+
stdio: 'ignore',
|
|
1057
|
+
detached: !isWindows,
|
|
1058
|
+
windowsHide: true,
|
|
1059
|
+
env: _spawnEnv,
|
|
1060
|
+
});
|
|
1061
|
+
if (_playingVoiceId !== voiceId) {
|
|
1062
|
+
try { playProc.kill(); } catch {}
|
|
1063
|
+
try { fs.unlinkSync(tempWav); } catch {}
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
_playingProcess = playProc;
|
|
1067
|
+
|
|
1068
|
+
playProc.on('exit', (code) => {
|
|
1069
|
+
if (_playingVoiceId !== voiceId) {
|
|
1070
|
+
try { fs.unlinkSync(tempWav); } catch {}
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1054
1073
|
if (code !== 0) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
setTimeout(() => { if (!_closed) { previewLine.setContent(_listFocused ? HINT_TEXT : ''); screen.render(); } }, 5000);
|
|
1074
|
+
// This player failed — try the next one
|
|
1075
|
+
_tryNextPlayer(rest);
|
|
1058
1076
|
} else {
|
|
1077
|
+
_playingVoiceId = null;
|
|
1078
|
+
_playingProcess = null;
|
|
1059
1079
|
previewLine.setContent(_listFocused ? HINT_TEXT : '');
|
|
1060
|
-
refreshDisplay();
|
|
1080
|
+
refreshDisplay();
|
|
1081
|
+
try { fs.unlinkSync(tempWav); } catch {}
|
|
1061
1082
|
}
|
|
1062
|
-
}
|
|
1063
|
-
try { fs.unlinkSync(tempWav); } catch {}
|
|
1064
|
-
});
|
|
1083
|
+
});
|
|
1065
1084
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
});
|
|
1085
|
+
playProc.on('error', () => {
|
|
1086
|
+
if (_playingVoiceId !== voiceId) { try { fs.unlinkSync(tempWav); } catch {} return; }
|
|
1087
|
+
_tryNextPlayer(rest);
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
_tryNextPlayer(_wavPlayers);
|
|
1074
1092
|
});
|
|
1075
1093
|
|
|
1076
1094
|
piper.on('error', () => {
|