Skip to content

fix(cli): play mp3 TTS on Linux via real decoders, not paplay (#1138) - #1180

Merged
murdore merged 1 commit into
releasefrom
fix/1138-tts-linux-mp3-playback
Jul 19, 2026
Merged

murdore merged 1 commit into
releasefrom
fix/1138-tts-linux-mp3-playback

Conversation

@murdore

@murdore murdore commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

What & why

Fixes #1138 — --tts-play mp3 playback is broken on Linux.

src/cli/utils/audioPlayer.ts routed all non-wav audio to paplay, but PulseAudio's paplay (and ALSA's aplay) only decode libsndfile/PCM formats (WAV/FLAC/Ogg/AIFF) — they cannot decode mp3/AAC. Since --tts-format defaults to mp3, the default neurolink generate "..." --tts --tts-play fails playback on Linux, with a misleading "Install PulseAudio (paplay) or ALSA (aplay)" message (PulseAudio is present; the format is simply undecodable).

Fix

  • playAudio now builds an ordered candidate list per platform + format and tries each until one succeeds (a missing binary or a decode failure advances to the next).
  • Linux, compressed formats (mp3/ogg/opus): lead with real decoders — ffplay (ffmpeg) → mpv → mpg123 (mp3 only) → cvlc (VLC) — then paplay/aplay as last-resort fallbacks (which still handle wav).
  • Linux wav: unchanged — aplay first, then paplay, then ffplay.
  • macOS / Windows: unchanged (afplay decodes everything; PowerShell paths preserved).
  • Error message is now format-aware: points at ffmpeg/mpv/mpg123/VLC or --tts-format wav, and lists what was tried.

Testing / proof

  • getPlayerCandidates(file, format, platform) is exported and platform-injectable, so the behavior is deterministically testable off-Linux.
  • New bugfixes-suite test asserts: Linux mp3 leads with a real decoder (never paplay/aplay); mpg123 is offered for mp3 but not opus; Linux wav still routes to aplay first; macOS uses afplay.
  • pnpm run check ✅ · pnpm run lint ✅ (0 errors) · bugfixes suite 90/90 PASS · pnpm run build:cli ✅.

Notes

  • CLI-only change; no SDK API surface touched. The helper type moved to src/lib/types/cli.ts as CliAudioPlayerCommand to satisfy the types-location lint rule.

Summary by CodeRabbit

  • Bug Fixes

    • Improved Linux TTS playback for MP3, OGG, and Opus audio by trying compatible decoders in sequence.
    • Added clearer playback errors that identify attempted players and explain when required decoders are unavailable.
    • Preserved reliable WAV playback through standard Linux audio systems.
  • Documentation

    • Added platform-specific TTS playback guidance for macOS and Linux.
    • Clarified decoder requirements and recommended WAV as a zero-dependency Linux fallback.

Copilot AI review requested due to automatic review settings July 16, 2026 22:52
@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Files reviewed: 3 (src/cli/utils/audioPlayer.ts, src/lib/types/cli.ts, test/continuous-test-suite-bugfixes.ts)

New issues raised:

  • 🔒 1 CRITICAL — potential shell injection through unescaped filePath passed to media players / PowerShell.
  • ⚠️ 2 MAJOR — internal use of the deprecated AudioFormat alias instead of TTSAudioFormat; non-ENOENT player failures are silently swallowed and retried.
  • 💡 3 MINOR/SUGGESTION — getAudioExtension duplicates existing format mapping; temp filename is predictable and can collide; test could tighten the first-candidate assertion.

Decision: REQUEST CHANGES

The CRITICAL shell-injection risk in getPlayerCandidates / playAudio blocks the PR. Even though filePath is normally generated internally, getPlayerCandidates is exported and platform-injectable, and several invoked players interpret shell metacharacters. Please sanitize/validate filePath before building command arguments, and properly escape the path for the PowerShell invocation.

Also please address the MAJOR issues:

  1. Replace AudioFormat with TTSAudioFormat in audioPlayer.ts (AudioFormat is a deprecated backward-compat alias in src/lib/types/tts.ts).
  2. Distinguish recoverable (missing binary) from unrecoverable (permission denied, decode failure, signal) player errors instead of unconditionally retrying every non-ENOENT failure.

Once these are fixed, the remaining MINOR items can be addressed at your discretion.

Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/lib/types/cli.ts
Comment thread test/continuous-test-suite-bugfixes.ts
@vercel

vercel Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
neurolink Ready Ready Preview, Comment Jul 18, 2026 9:15am

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

murdore commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

✅ Verification & gap-bridging (hardened)

Rebased onto current release and hardened across test / docs / proof gaps.

Test gap → closed

  • Unit (routing): getPlayerCandidates(file, format, platform) — asserts Linux mp3 leads with real decoders (ffplay/mpv/mpg123/cvlc), never paplay/aplay; mpg123 is offered for mp3 but not opus; Linux wav routes to aplay first; macOS uses afplay.
  • User-facing error (new): buildPlaybackErrorMessage is now exported and tested — the message a Linux user sees with no decoder installed names ffmpeg/mpv/mpg123/VLC and the --tts-format wav fallback, and explains that paplay/aplay cannot decode mp3 — not the old misleading "install PulseAudio" (PulseAudio is present; it simply can't decode mp3).
  • Both tests are platform-injectable, so the Linux behavior is verified deterministically off-Linux.

Why not a full end-to-end --tts-play test

Real playback needs both a provider API key (to synthesize) and audio hardware/a decoder on the runner, so a true E2E neurolink generate … --tts --tts-play assertion isn't hermetic. The two tests above cover the exact decision (which player, in what order) and the exact user-facing output (the error text) — the two things the fix changes — without those dependencies.

Docs gap → closed

docs/features/tts.md "Platform-Specific Considerations" previously claimed "All formats supported / ffplay (Linux) handle all formats" — inaccurate, since the old code routed mp3 to paplay. Now split into explicit macOS/Linux guidance stating that Linux compressed-format playback needs a decoder (ffmpeg/mpv/mpg123/VLC) or --tts-format wav.

Enhancement gap

None outstanding — the decoder chain + actionable error is the complete fix. (No new dependency added; playback stays best-effort and non-fatal.)

Proof

pnpm run check ✅ (0 errors) · pnpm run lint ✅ (0 errors) · prettier --check ✅ · both #1138 bugfixes-suite tests green. The two failing updater: tests in a local run are pre-existing environmental cases in the upstream suite (present on release, and test (20) was green on this PR) — unrelated to this change.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@murdore, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 5 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cfb516f7-f06c-458f-9f7b-e22abea471f7

📥 Commits

Reviewing files that changed from the base of the PR and between 16e3600 and 05d60f9.

📒 Files selected for processing (4)
  • docs/features/tts.md
  • src/cli/utils/audioPlayer.ts
  • src/lib/types/cli.ts
  • test/continuous-test-suite-bugfixes.ts
📝 Walkthrough

Walkthrough

Changes

TTS playback

Layer / File(s) Summary
Format-aware player candidates
src/lib/types/cli.ts, src/cli/utils/audioPlayer.ts
Adds a shared audio-player command type and selects ordered platform- and format-specific playback candidates.
Playback retries and errors
src/cli/utils/audioPlayer.ts
Attempts candidates sequentially, records failures, and reports platform- and format-aware playback errors.
Linux validation and guidance
test/continuous-test-suite-bugfixes.ts, docs/features/tts.md
Tests Linux MP3 decoder ordering and error messages, and documents WAV and compressed-format playback requirements.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: sachinsharma-juspay, pdogra1299

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main Linux MP3 playback fix.
Linked Issues check ✅ Passed The changes address #1138 by using real Linux decoders for MP3, preserving WAV playback, and improving error messaging.
Out of Scope Changes check ✅ Passed The docs and tests are directly related to the playback fix and do not appear unrelated to the issue scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1138-tts-linux-mp3-playback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

github-actions Bot commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Documentation Validation Results

🚀 Documentation validation passed!

Check Status Result
Frontmatter Validation ✅ Passed
TypeScript Check ✅ Passed
Build ✅ Passed
Link Validation ✅ Passed

📦 Build artifact uploaded successfully. Ready for deployment preview.

Commit: f409ee639f57ec3e153fa6440c3b7edd9c082b91 | Workflow: View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/continuous-test-suite-bugfixes.ts (1)

3858-3934: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Exercise the actual retry loop.

These tests validate candidate lists and messages, but not that playAudio continues after a failed player. Add an injectable command executor or mock execFile and cover first-fails/second-succeeds plus all-fail attempt aggregation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/continuous-test-suite-bugfixes.ts` around lines 3858 - 3934, Extend the
audio playback tests around playAudio to exercise the actual retry loop by
injecting or mocking the command executor/execFile. Add coverage where the first
candidate fails and the second succeeds, then verify all candidates failing
produces an aggregated error containing each attempted player and its failure.
Keep the existing candidate-order and message assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/features/tts.md`:
- Around line 318-320: Update the WAV playback statements in the TTS
documentation to avoid implying zero dependencies or out-of-the-box support.
State that WAV requires an available aplay, paplay, or ffplay executable in
PATH, while clarifying that it does not require a compressed-format decoder.

In `@src/cli/utils/audioPlayer.ts`:
- Around line 153-157: Update the Linux non-WAV error message in the audio
playback format branch to apply the “paplay/aplay cannot decode” limitation and
mpg123 recommendation only when format is MP3. Preserve the existing
format-specific guidance for OGG/Opus from the earlier playback logic, while
retaining the WAV message and fallback player recommendations for other formats.

---

Nitpick comments:
In `@test/continuous-test-suite-bugfixes.ts`:
- Around line 3858-3934: Extend the audio playback tests around playAudio to
exercise the actual retry loop by injecting or mocking the command
executor/execFile. Add coverage where the first candidate fails and the second
succeeds, then verify all candidates failing produces an aggregated error
containing each attempted player and its failure. Keep the existing
candidate-order and message assertions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eafd20ef-fd55-428b-a220-65bb1fc91dc4

📥 Commits

Reviewing files that changed from the base of the PR and between c5515be and 16e3600.

📒 Files selected for processing (4)
  • docs/features/tts.md
  • src/cli/utils/audioPlayer.ts
  • src/lib/types/cli.ts
  • test/continuous-test-suite-bugfixes.ts

Comment thread docs/features/tts.md Outdated
Comment thread src/cli/utils/audioPlayer.ts Outdated

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Files reviewed: 4 (docs/features/tts.md, src/cli/utils/audioPlayer.ts, src/lib/types/cli.ts, test/continuous-test-suite-bugfixes.ts)

New issues raised this run:

  • 🔒 1 CRITICAL — potential shell injection through unescaped filePath passed to media players / PowerShell in the exported getPlayerCandidates helper.
  • ⚠️ 2 MAJOR — internal use of the deprecated AudioFormat alias instead of TTSAudioFormat; external player processes have no timeout, so a hung decoder can block the CLI indefinitely.
  • 💡 1 MINOR — temp filename uses only Date.now() and can collide under rapid/concurrent calls.

Existing review comments already covered: I did not duplicate points already raised by prior reviewers (deprecated AudioFormat, temp-file predictability, swallowed non-ENOENT errors, test coverage gaps, docs wording). The issues above are the ones I found independently or that remain unaddressed.

Decision: REQUEST CHANGES

The CRITICAL shell-injection risk in getPlayerCandidates / playAudio blocks the PR. Even though filePath is normally generated internally, getPlayerCandidates is exported and platform-injectable, and several invoked players interpret shell metacharacters. Please sanitize/validate filePath before building command arguments, and properly escape the path for the PowerShell invocation.

Also please address the MAJOR issues:

  1. Replace AudioFormat with TTSAudioFormat in audioPlayer.ts (AudioFormat is a deprecated backward-compat alias in src/lib/types/tts.ts).
  2. Add a timeout to execFileAsync (e.g. timeout: 30_000) so a hung ffplay/mpv/cvlc cannot block the CLI forever.

Once these are fixed, the remaining MINOR item can be addressed at your discretion.

Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/cli/utils/audioPlayer.ts
Comment thread src/cli/utils/audioPlayer.ts
paplay (PulseAudio) and aplay (ALSA) only decode libsndfile/PCM formats and cannot decode mp3, yet --tts-format defaults to mp3 — so 'neurolink generate ... --tts --tts-play' silently failed playback on Linux with a misleading 'install PulseAudio/ALSA' message.

audioPlayer now builds an ordered candidate list per platform+format and tries each until one succeeds. On Linux, compressed formats (mp3/ogg/opus) lead with real decoders (ffplay/mpv/mpg123 for mp3/cvlc); paplay/aplay remain last-resort fallbacks and still handle wav. The failure message is format-aware (names ffmpeg/mpv/mpg123/VLC or --tts-format wav).

getPlayerCandidates(file, format, platform) and buildPlaybackErrorMessage are exported and platform-injectable for deterministic tests. Bugfixes-suite tests assert: Linux mp3 leads with a decoder (not paplay/aplay), mpg123 offered for mp3 not opus, wav routes to aplay, macOS uses afplay; and the user-facing error names the decoders + wav fallback rather than the misleading PulseAudio message. Docs (docs/features/tts.md) updated with the Linux decoder requirement.
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Reviewed all 4 changed files in PR #1180.

Files examined:

  • docs/features/tts.md
  • src/cli/utils/audioPlayer.ts
  • src/lib/types/cli.ts
  • test/continuous-test-suite-bugfixes.ts

New issues raised this run: None.

Existing review threads: All prior review comments are resolved (including the AudioFormat → TTSAudioFormat migration, PowerShell single-quote escaping, temp-file collision avoidance, player timeout wiring, and format-aware error messages). No duplicates or re-raises.

Assessment: This is a focused CLI-only fix for Linux mp3 TTS playback. The implementation correctly routes compressed formats through real decoders before falling back to paplay/aplay, uses execFile (not a shell) for player invocation, escapes PowerShell single quotes, caps player execution with a timeout, and includes deterministic regression tests. No blocking security, architecture, or backward-compatibility concerns remain.

Approving.

@murdore
murdore merged commit a5a9a16 into release Jul 19, 2026
19 checks passed
@murdore
murdore deleted the fix/1138-tts-linux-mp3-playback branch July 19, 2026 11:04
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 9.94.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

This branch was successfully deployed

1 active deployment
Preview — 05d60f96 Deployed Jul 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TTS --tts-play: mp3 playback broken on Linux (paplay can't decode mp3; default format)

3 participants