Text-to-Speech Plugin for NativePHP Mobile#
On-device text-to-speech. PHP and JavaScript ask the OS to speak a string.
Overview#
partek/text-to-speech calls the operating system's own speech engine from a NativePHP Mobile app. iOS uses AVSpeechSynthesizer. Android uses android.speech.tts.TextToSpeech. There is no cloud TTS, no window.speechSynthesis wrapper, and no extra permissions.
You call the facade from a Super Native press handler, a Livewire action, or Blade. The same methods are exported for JavaScript. Register the plugin once; there is no boot-time setup after that.
Buy this when you need the device to read a string aloud — confirmations, accessibility readbacks, spoken prompts — without adding a speech vendor, a network dependency, or microphone/camera entitlements.
Installation#
composer require partek/text-to-speech
Don't forget to register the plugin:
php artisan native:plugin:register partek/text-to-speech
Usage#
PHP (Super Native)#
Call the facade from a Super Native screen method. A button or pressable is enough.
use Native\Mobile\Attributes\On;use Native\Mobile\NativeComponent;use PARTek\TextToSpeech\Events\SpeechFailed;use PARTek\TextToSpeech\Events\SpeechFinished;use PARTek\TextToSpeech\Facades\TextToSpeech; class ReadAloud extends NativeComponent{ public function speakPassage(): void { TextToSpeech::speak('The oven is preheated to 220 degrees.', 'en-US', 0.5, 1.0); } public function stopSpeaking(): void { TextToSpeech::stop(); } #[On(SpeechFinished::class)] public function onSpeechFinished(?string $text = null): void { // Utterance finished } #[On(SpeechFailed::class)] public function onSpeechFailed(string $message, ?string $text = null): void { // Engine rejected the utterance }}
<native:column class="w-full p-4 gap-3"> <native:button label="Speak" @press="speakPassage" /> <native:button label="Stop" @press="stopSpeaking" /></native:column>
PHP (Livewire / Blade)#
use Native\Mobile\Attributes\OnNative;use PARTek\TextToSpeech\Events\SpeechStarted;use PARTek\TextToSpeech\Facades\TextToSpeech; TextToSpeech::speak('Hello from the device.');TextToSpeech::speak('Hello from the device.', 'en-GB', 0.45, 1.0); TextToSpeech::pause();TextToSpeech::resume();TextToSpeech::stop(); $speaking = TextToSpeech::isSpeaking();
#[OnNative(SpeechStarted::class)]public function onSpeechStarted(?string $text = null): void{ // Speech started}
Empty or whitespace-only text returns false and does not call the native bridge.
JavaScript (Vue / React / Inertia)#
import { speak, stop, pause, resume, isSpeaking } from 'vendor/partek/text-to-speech/resources/js/index.js'; await speak('Hello from the device.', 'en-GB', 0.5, 1.0); if (await isSpeaking()) { await pause(); await resume(); await stop();}
The JS helper POSTs to /_native/api/call. If the document has a CSRF token (meta[name="csrf-token"] or input[name="_token"]), it is sent as X-CSRF-TOKEN. Bridge errors throw.
Events#
The native engines emit Laravel events the NativePHP bridge can deliver to the live Super Native screen or a Livewire component.
| Event | When | Payload |
|---|---|---|
PARTek\TextToSpeech\Events\SpeechStarted |
Utterance begins | ?string $text |
PARTek\TextToSpeech\Events\SpeechFinished |
Utterance completes | ?string $text |
PARTek\TextToSpeech\Events\SpeechFailed |
Engine or utterance fails | string $message, ?string $text |
stop() cancels the current utterance. That is not a finish and not a failure.
Super Native#
use Native\Mobile\Attributes\On;use PARTek\TextToSpeech\Events\SpeechFinished; #[On(SpeechFinished::class)]public function onSpeechFinished(?string $text = null): void{ // ...}
Livewire#
use Native\Mobile\Attributes\OnNative;use PARTek\TextToSpeech\Events\SpeechFinished; #[OnNative(SpeechFinished::class)]public function onSpeechFinished(?string $text = null): void{ // ...}
Methods#
speak(string $text, ?string $locale = null, ?float $rate = null, ?float $pitch = null): bool#
Starts speaking. A new call replaces the current utterance.
| Parameter | Type | Required | Description |
|---|---|---|---|
$text |
string |
yes | Text to speak. Empty / whitespace fails closed (false, no bridge call). |
$locale |
?string |
no | BCP-47 voice tag such as en-US or en-GB. Omitted uses the system voice. |
$rate |
?float |
no | Speech rate on the iOS AVSpeechUtterance scale 0.1–1.0 (typical default 0.5). Values outside the range are clamped. Non-finite values become 0.5. |
$pitch |
?float |
no | Pitch 0.5–2.0 (default 1.0). Values outside the range are clamped. Non-finite values become 1.0. |
Returns true when the bridge accepted the request. Speech itself is asynchronous — listen for events for start/finish/failure.
Rate mapping
PHP / iOS rate |
iOS AVSpeechUtterance.rate |
Android setSpeechRate |
|---|---|---|
0.5 (default) |
0.5 |
1.0 (normal) |
0.25 |
0.25 |
0.5 |
1.0 |
1.0 |
2.0 |
Android rate = iOS rate × 2.0.
stop(): bool#
Stops speaking and discards the queue.
pause(): bool#
Pauses if the platform supports it. See Platform behavior.
resume(): bool#
Resumes a paused utterance if the platform supports it.
isSpeaking(): bool#
Returns the engine's current speaking flag when the bridge can answer synchronously. If a host cannot surface the value, this returns false — listen for SpeechStarted and SpeechFinished instead.
Platform behavior#
| iOS | Android | |
|---|---|---|
| Engine | AVSpeechSynthesizer |
android.speech.tts.TextToSpeech |
| Minimum | iOS 18.0 | API 26 |
| Permissions | none | none |
speak() |
Stops any current/paused utterance, then speaks | QUEUE_FLUSH replaces the current utterance |
pause() |
pauseSpeaking(at: .immediate) |
No reliable pause API. The call is accepted and may no-op. stop() is the real cancel. |
resume() |
continueSpeaking() |
No-op when pause was a no-op |
isSpeaking() |
synthesizer.isSpeaking (true while speaking or paused) |
TextToSpeech.isSpeaking (true only while audio is playing) |
| Locale | AVSpeechSynthesisVoice(language:) |
setLanguage(Locale.forLanguageTag) — unsupported tags fall back to the default voice |
| Events | Synthesizer delegate | UtteranceProgressListener |
Android TTS initializes asynchronously on first speak(). The bridge returns once the utterance is queued. Init failure emits SpeechFailed.
Some Android engines cap utterance length (commonly around 4,000 characters). Split long copy into multiple speak() calls if a device rejects the text.
Voices, accents, and quality are the OS engine's. This plugin does not download voices or ship a third-party TTS SDK.
Testing#
PHPUnit covers the PHP contract only: facade argument shaping, fail-closed empty text, and bridge method names against nativephp.json. There is no fake device and no hardware run in this repository.
composer installcomposer test
composer validate should pass. Native paths (resources/android/src/com/partek/plugins/texttospeech/*.kt, resources/ios/Sources/*.swift, resources/js/index.js) are in place so php artisan native:plugin:validate can resolve them from a NativePHP app.
This package has not been claimed as device-verified.