Speech-to-Text Plugin for NativePHP Mobile#
On-device dictation / speech recognition for NativePHP Mobile on iOS and Android.
Overview#
PHP starts a listen session; the OS speech recognizer turns the microphone into text and delivers it as events. listen() does not return the transcript synchronously.
Call SpeechToText::listen() from a screen or component (for example a mic button). The native layer runs SFSpeechRecognizer on iOS and SpeechRecognizer / RecognizerIntent on Android, then fires SpeechPartial, SpeechResult, SpeechFailed, or PermissionDenied. stop() ends the session and keeps the final text. cancel() ends it and discards.
This is not the official microphone plugin: that records an audio file. This plugin returns text and does not save recordings.
This is not vikas5914/fluidaudio: that package is iOS-only Apple Core ML. This plugin uses the platform speech APIs on both iOS and Android. No extra ML Kit or CocoaPods dependency.
Installation#
composer require partek/speech-to-text
Don't forget to register the plugin:
php artisan native:plugin:register partek/speech-to-text
Usage#
PHP (Super Native)#
use Native\Mobile\Attributes\On;use PARTek\SpeechToText\Events\PermissionDenied;use PARTek\SpeechToText\Events\SpeechFailed;use PARTek\SpeechToText\Events\SpeechPartial;use PARTek\SpeechToText\Events\SpeechResult;use PARTek\SpeechToText\Facades\SpeechToText; class DictateScreen extends NativeComponent{ public string $transcript = ''; public bool $listening = false; public function startDictation(): void { $this->listening = SpeechToText::listen(); } public function stopDictation(): void { SpeechToText::stop(); $this->listening = false; } public function cancelDictation(): void { SpeechToText::cancel(); $this->listening = false; $this->transcript = ''; } #[On(SpeechPartial::class)] public function onPartial(string $text): void { $this->transcript = $text; } #[On(SpeechResult::class)] public function onResult(string $text, bool $isFinal): void { $this->transcript = $text; if ($isFinal) { $this->listening = false; } } #[On(SpeechFailed::class)] public function onFailed(string $message): void { $this->listening = false; } #[On(PermissionDenied::class)] public function onPermissionDenied(?string $message = null): void { $this->listening = false; }}
Optional locale (BCP 47) and partial results:
SpeechToText::listen('en-US');SpeechToText::listen('en-US', false);
JavaScript (Vue/React/Inertia)#
import { listen, stop, cancel } from 'partek-speech-to-text'; await listen();await listen('en-US', true);await stop();await cancel();
The CSRF token is read from the page <meta name="csrf-token">. Bridge errors throw.
Events#
SpeechPartial#
Fired while the user is speaking when partial is true.
| Payload | Type | Description |
|---|---|---|
text |
string | Current hypothesis |
SpeechResult#
Fired when the session produces a transcript to keep (typically after stop() or end of speech).
| Payload | Type | Description |
|---|---|---|
text |
string | Recognized text |
isFinal |
bool | true when this is the session's final result |
SpeechFailed#
Fired when recognition cannot start or fails.
| Payload | Type | Description |
|---|---|---|
message |
string | Reason |
PermissionDenied#
Fired when speech recognition or microphone permission is denied. The plugin does not continue silently.
| Payload | Type | Description |
|---|---|---|
message |
string|null | Optional reason |
Methods#
listen(?string $locale = null, bool $partial = true): bool#
Starts a listen session. Returns whether the native layer accepted the request. The transcript is not in the return value.
| Parameter | Type | Description |
|---|---|---|
locale |
string|null | BCP 47 tag such as en-US. null or empty uses the device locale. |
partial |
bool | When true, dispatch SpeechPartial while the user is speaking. Default true. |
stop(): bool#
Stops the session and keeps the final transcript (SpeechResult).
cancel(): bool#
Stops the session and discards the transcript. No SpeechResult is dispatched.
Platform behavior#
On-device vs network#
- iOS: Uses
SFSpeechRecognizer. When the locale supports on-device recognition (supportsOnDeviceRecognition), the request requires on-device. If that locale is not available on-device, Apple's recognizer may fall back to network (audio sent to Apple). - Android: Uses
SpeechRecognizerwith aRecognizerIntent. When on-device recognition is available (API 31+), the plugin preferscreateOnDeviceSpeechRecognizerandEXTRA_PREFER_OFFLINE. Otherwise the system recognizer may use the network.
Permission prompts#
- Android: Runtime
RECORD_AUDIO. Denied →PermissionDenied. - iOS: Speech recognition (
NSSpeechRecognitionUsageDescription) and microphone (NSMicrophoneUsageDescription). Either denied →PermissionDenied.
Android intent vs iOS silent#
- iOS: In-app and silent. There is no system dictation sheet.
- Android: In-app
SpeechRecognizerdriven by aRecognizerIntent(not the full Google voice-search activity).stop()/cancel()stay in your screen. Some devices still show a system listening indicator.
Silence timeout#
- iOS:
SFSpeechRecognizerkeeps listening until you callstop()orcancel()- no automatic cutoff on a pause. - Android:
RecognizerIntentends the session after silence. This plugin setsEXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLISandEXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLISto 3000ms (up from the OS default of ~1000ms), confirmed on-device (Galaxy A54) to fix a real cutoff mid-sentence during a normal pause for breath. Some OEM builds may still apply their own shorter limit on top of this.
Testing#
Package tests cover facade listen() / stop() argument shaping, the empty-locale default, and that the event classes exist. They do not play device audio.
composer test