fix: TTS playback, screen wake lock, and TTS phrase caching#9
Merged
Conversation
**TTS not playing (audio.js + TTSBridge.kt)**
- audio.js was checking window.NativePhp?.tts?.speak which never existed
in the Android codebase — no Kotlin bridge was ever installed for it.
- Browser speechSynthesis fallback also silently failed because
getVoices() returns [] synchronously; code never waited for the
voiceschanged event, so no voice was selected.
- Fix JS side: check window.AndroidTTS?.speak (native Kotlin bridge,
see PR description for Kotlin patch) and fix the browser fallback to
wait for voiceschanged before calling speak().
**Screen wake lock never acquired (app.js + TimerScreen.php + Settings.php)**
- keepScreenOn setting was saved to DB (default true) but never consumed:
TimerScreen.php did not expose it as a Livewire property, Settings.php
did not include it in the settingsLoaded event, and app.js had no
wake lock code whatsoever.
- Fix: expose keepScreenOn on TimerScreen, pass it through settingsLoaded,
and use navigator.wakeLock.request('screen') in timerAudio Alpine
component tied to timer state transitions (acquire on active states,
release on idle/complete). Re-acquires automatically if OS releases it.
**TTS caching (TTSBridge.kt)**
- Once the native Kotlin bridge is applied, TTSBridge pre-synthesizes all
known phrases (Done, Go, Next, Get ready, 3/2/1 …) to WAV files in the
app cache directory on first launch and reloads them via MediaPlayer.
- Cache TTL is 3 days; stale/missing files are re-synthesized automatically.
- Known phrases play instantly with zero TTS engine startup latency.
Dynamic labels (e.g. custom countdown text) fall back to live TTS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Android WebView, speechSynthesis.voiceschanged may never fire, which caused the speak() fallback path to register a listener that never ran -- effectively silencing all TTS when window.AndroidTTS is not yet available (i.e. before the APK is rebuilt with TTSBridge.kt). Fix: speak immediately using whatever voices are already loaded; the default voice is used if no en-* voice is found, which is correct behaviour on Android where voice selection is handled by the TTS engine. Also replace smart-quote/em-dash characters in comments with plain ASCII to prevent future string-match failures in automated tooling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**Demo HIIT program (seeder)** - composer.json: add Database\\Seeders\\ to production autoload.psr-4 so the seeder is available in the embedded NativePHP runtime. - database/seeders/DatabaseSeeder.php: new seeder that creates the demo HIIT program (Warmup 10s / Sprint 8sx3 / Stretch 8s) when the programs table is empty. Idempotent via Program::exists() guard. Calls Setting::current() first so the settings row exists before Program construction touches it. - AppServiceProvider::boot(): call DatabaseSeeder wrapped in try/catch so the inevitable "table does not exist" error during `artisan migrate` itself is swallowed gracefully and succeeds on the next boot. **TTS diagnostic logging** JS (app.js + audio.js): - timerAudio init(): logs soundMode, keepScreenOn, and whether window.AndroidTTS is already present at page-load time. - playBeep listener: logs voiceText() output before calling speak() so an empty countdownLabel is visible before the call. - speak(): logs which path is taken (AndroidTTS bridge / speechSynthesis / neither), voice count + selection, and attaches utt.onerror so SpeechSynthesisUtterance failures surface in the console instead of silently disappearing. Kotlin (TTSBridge.kt + WebViewManager.kt -- gitignored): - WebViewManager: log after AndroidTTS bridge registration. - TTSBridge.speak(): entry log with text always fires (was missing). - TTSBridge.speak(): explicit cache-miss log before speakLive(). - TTSBridge.speakLive(): tts null-check with error log. - TTSBridge.prebuildCache(): log phrase count at start. - All Kotlin log messages prefixed [TTS] for easy Logcat filtering: adb logcat -s PHPMonitor:D PHPMonitor-Console:D TTSBridge:D Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a path-repository package that wraps Android TTS (and a Web Speech API fallback) into a NativePHP-style bridge (Kotlin + PHP + JS). Registers it via a new NativeServiceProvider and integrates it into Settings with an updateAndTest() action for in-app voice testing. Also bumps nativephp/mobile to 3.2.3 and pins axios to 1.15.0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
window.NativePhp.tts.speakwas called but that object never existed in the codebase; browserspeechSynthesisfallback also failed silently becausegetVoices()returns[]synchronously and the code never waited for thevoiceschangedevent.keepScreenOnsetting was stored in the DB (defaulttrue) but never consumed:TimerScreen.phpdidn't expose it as a Livewire property,Settings.phpdidn't include it insettingsLoaded, andapp.jshad zero wake lock code.MediaPlayerwith zero latency on subsequent runs.What was changed (committed)
resources/js/audio.jsspeak()now useswindow.AndroidTTS?.speak(Kotlin bridge); browser fallback waits forvoiceschangedevent before callingspeechSynthesis.speak()resources/js/app.jskeepScreenOnproperty +navigator.wakeLock.request/releasetied to timer state; addedsettingsLoadedevent listener to sync settings changes from Settings screenapp/Livewire/TimerScreen.phppublic bool $keepScreenOnloaded from settings inmount()app/Livewire/Settings.phpkeepScreenOntosettingsLoadeddispatchKotlin changes required in
nativephp/android/(gitignored)The
/nativephpdirectory is gitignored so these three changes must be applied manually after pulling this branch.1. Create
nativephp/android/app/src/main/java/com/nativephp/mobile/bridge/TTSBridge.kt2. Edit
WebViewManager.kt— addTTSBridgeimport + field + registration + shutdown3. Edit
MainActivity.kt— callwebViewManager.shutdown()inonDestroy()Test plan
🤖 Generated with Claude Code