Skip to content

feat: add sentence-chunked text-to-speech playback#1496

Open
agent-p1p wants to merge 2 commits into
masterfrom
pip/whitenoise-android-1480
Open

feat: add sentence-chunked text-to-speech playback#1496
agent-p1p wants to merge 2 commits into
masterfrom
pip/whitenoise-android-1480

Conversation

@agent-p1p

@agent-p1p agent-p1p commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • derive Speak eligibility from MessageProjector.copyableText, including edited text and caption-only media handling
  • split text with locale-aware BreakIterator boundaries and engine-safe word-boundary fallbacks
  • add a deterministic TTS queue/controller for progress, pause/resume, sentence skipping, stale callback rejection, and network-specific failures

Test plan

  • compile both dev debug distributions
  • run both full dev debug unit-test suites
  • run ktlint
  • run Android lint for both dev debug distributions

The engine resolver/audio-focus implementation remains in #1479. The conversation action and transport UI consume this controller in #1481.

Fixes #1480


Open in Stage

Summary by CodeRabbit

  • New Features

    • Added text-to-speech playback with sentence-aware chunking for long messages.
    • Added playback controls including pause, resume, stop, next, and previous.
    • Added playback status reporting and handling for network or speech synthesis errors.
    • Added support for identifying whether messages contain speakable content.
  • Bug Fixes

    • Pending media placeholders without captions are no longer treated as speakable or copyable text.
    • Improved handling of speech interruptions, engine changes, and stale playback events.
  • Tests

    • Added coverage for text chunking, playback controls, error handling, and pending media behavior.

Reuse copyable message text, split it into engine-safe chunks, and expose deterministic pause, resume, skip, progress, and error state.

Fixes #1480
@stage-review

stage-review Bot commented Jul 16, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 4 individual chapters for you:

Title
1 Define speakable text criteria in MessageProjector
2 Implement locale-aware text chunking for TTS
3 Create deterministic TTS playback queue state machine
4 Wire TTS controller with engine and audio focus
Open in Stage

Chapters generated by Stage for commit 47dc95f on Jul 16, 2026 5:05pm UTC.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 2 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e01c6ea9-3aa5-4f78-8d2a-a34c088d8426

📥 Commits

Reviewing files that changed from the base of the PR and between 4cea722 and 47dc95f.

📒 Files selected for processing (6)
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsChunker.kt
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsController.kt
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsPlaybackQueue.kt
  • app/src/test/java/dev/ipf/whitenoise/android/audio/tts/TtsChunkerTest.kt
  • app/src/test/java/dev/ipf/whitenoise/android/audio/tts/TtsControllerTest.kt
  • app/src/test/java/dev/ipf/whitenoise/android/audio/tts/TtsPlaybackQueueTest.kt

Walkthrough

Changes

TTS read-aloud foundation

Layer / File(s) Summary
Speakable text filtering
app/src/main/java/dev/ipf/whitenoise/android/core/MessageProjector.kt, app/src/test/.../core/MessageProjectorTest.kt
Pending-media placeholders are excluded unless caption text exists, and canSpeak mirrors copyableText.
Sentence chunking and boundaries
app/src/main/java/.../audio/tts/TtsChunker.kt, app/src/test/.../audio/tts/TtsChunkerTest.kt
Text is split into indexed, locale-aware, length-bounded chunks while preserving whitespace and UTF-16 surrogate integrity.
Playback queue state machine
app/src/main/java/.../audio/tts/TtsPlaybackQueue.kt, app/src/test/.../audio/tts/TtsPlaybackQueueTest.kt
Queue state tracks speaking progress, pause/resume, skipping, completion, stale callbacks, and network or synthesis errors.
Controller and Android engine integration
app/src/main/java/.../audio/tts/TtsController.kt, app/src/main/java/.../audio/tts/AndroidTtsSpeechEngine.kt, app/src/test/.../audio/tts/TtsControllerTest.kt
The controller coordinates chunk playback, engine lifecycle, Android TTS callbacks, and audio-focus acquisition and release.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • #1478 — Adds the native TTS read-aloud foundation described by this change.
  • #1481 — Defines TTS APIs used by the Speak action and transport bar.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR satisfies [#1480], but [#1479] engine resolution, trust warning, and shared audio-focus ownership are missing. Add the #1479 engine resolver, trust-warning flow, and shared audio-focus handling, or remove #1479 from the linked scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 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 describes the main change: sentence-chunked TTS playback.
Out of Scope Changes check ✅ Passed All changes align with the stated TTS playback and speakable-text objectives; no unrelated functionality appears in the summary.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pip/whitenoise-android-1480

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

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Preview APK for PR #1496

Home-screen label: 1496 White Noise with the blueprint launcher icon — installs side-by-side with the production and staging apps.

Install directly — one tap, no GitHub sign-in (public, content-addressed .apk). ARM64 only.

Every push to this PR rebuilds and updates this comment.

@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

🤖 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 `@app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsController.kt`:
- Around line 74-87: Update TtsController.speak to inspect the status returned
by activeEngine.setLanguage(locale) before calling queue.start(chunks); for any
non-available result, report TtsState.Error and return false without starting
playback. Add coverage in TtsControllerTest for unsupported locales and verify
the queue is not started.

In `@app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsPlaybackQueue.kt`:
- Around line 126-144: Update the error mapping in TtsPlaybackQueue.onError so
both TextToSpeech.ERROR_NETWORK and TextToSpeech.ERROR_NETWORK_TIMEOUT produce
TtsError.Network, while other codes remain TtsError.Synthesis. Extend
networkFailureIsDistinctFromOtherSynthesisFailures to cover the timeout code.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4ee371c-65ac-41cc-ad3f-33097f6684af

📥 Commits

Reviewing files that changed from the base of the PR and between 5c6654d and 4cea722.

📒 Files selected for processing (9)
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/AndroidTtsSpeechEngine.kt
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsChunker.kt
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsController.kt
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsPlaybackQueue.kt
  • app/src/main/java/dev/ipf/whitenoise/android/core/MessageProjector.kt
  • app/src/test/java/dev/ipf/whitenoise/android/audio/tts/TtsChunkerTest.kt
  • app/src/test/java/dev/ipf/whitenoise/android/audio/tts/TtsControllerTest.kt
  • app/src/test/java/dev/ipf/whitenoise/android/audio/tts/TtsPlaybackQueueTest.kt
  • app/src/test/java/dev/ipf/whitenoise/android/core/MessageProjectorTest.kt

@agent-p1p

Copy link
Copy Markdown
Contributor Author

BLOCKING:

  • Sentence splitting only repairs a title abbreviation when the abbreviation is the entire BreakIterator candidate: app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsChunker.kt:39-46. With the same Locale.US iterator used by the tests, "I met Dr. Smith in Washington. He arrived." yields candidates [I met Dr. ], [Smith in Washington. ], and [He arrived.]; because "I met Dr." is not equal to "dr.", chunk() emits three chunks instead of two. That creates an artificial pause/progress/skip boundary in a common valid sentence and defeats the abbreviation-aware behavior this issue requires. Add this mid-sentence-title regression case and make abbreviation handling work when the title ends a larger candidate, not only when it starts the whole message.

SUGGESTION:

  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsController.kt:84 ignores setLanguage(locale)'s result. If the engine returns LANG_MISSING_DATA or LANG_NOT_SUPPORTED, playback is still queued and may use a previous/default language. Reject or surface that condition before starting the queue, and balance the already-acquired audio focus.
  • app/src/main/java/dev/ipf/whitenoise/android/audio/tts/TtsPlaybackQueue.kt:139 maps only ERROR_NETWORK to TtsError.Network; Android also exposes ERROR_NETWORK_TIMEOUT. A cloud engine timeout is still the network-specific failure the state is meant to explain, so classify and test both codes.

NITPICK:
none

SUMMARY: The copyable-text matrix, long-input splitting, queue generation handling, pause/resume, skip behavior, completion progress, and exact ERROR_NETWORK path are well covered and passed independently (41 targeted tests). One sentence-boundary defect remains: title abbreviations work only at the beginning of a BreakIterator span, not in the middle of an otherwise normal sentence.

VERDICT: REQUEST_CHANGES
SENSITIVE_PATHS: none

Handle title abbreviations within sentence candidates, reject unsupported TTS locales before queueing, and classify network timeouts as network failures.
@agent-p1p

Copy link
Copy Markdown
Contributor Author

Addressed the blocking review and both suggestions in 47dc95f:

  • Mid-sentence title abbreviations now merge with the following BreakIterator candidate; the reported “I met Dr. Smith…” case stays at two chunks.
  • Unsupported locale results stop before queueing, surface TtsState.Error, and release audio focus.
  • ERROR_NETWORK_TIMEOUT now maps to the network-specific error state.

Added regressions for all three. Full local Android CI command set passes for both Zapstore and Play flavors.

@agent-p1p
agent-p1p marked this pull request as ready for review July 16, 2026 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TTS: speakable-text extraction and sentence chunking

1 participant