I'm using these libraries:
https://github.com/jimmckeeth/FireMonkey-Android-Voice
https://github.com/FMXExpress/android-object-pascal-wrapper/tree/master/android-25
Here is my source code:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
SpeechRecognition, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo,
FMX.StdCtrls, FMX.Memo.Types, AndroidTTS, FMX.Media, System.IOUtils;
type
TForm1 = class(TForm)
SpeechRecognition1: TSpeechRecognition;
Memo1: TMemo;
Button1: TButton;
AndroidTTS1: TAndroidTTS;
Button2: TButton;
Button3: TButton;
Button4: TButton;
MediaPlayer1: TMediaPlayer;
Button5: TButton;
procedure SpeechRecognition1Command(Sender: TObject; Guess: string);
procedure SpeechRecognition1Recognition(Sender: TObject; Guess: string);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{$R *.NmXhdpiPh.fmx ANDROID}
procedure TForm1.Button1Click(Sender: TObject);
begin
SpeechRecognition1.Prompt := 'What you want?';
SpeechRecognition1.Listen;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
AndroidTTS1.setSpeechRate(0.75);
AndroidTTS1.SpeakVolume('Hello, whats up?',0.75);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
MediaPlayer1.Volume := 0.25;
MediaPlayer1.FileName := TPath.GetDocumentsPath + PathDelim + 'HerosOfLegend.wav';
MediaPlayer1.Play;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
MediaPlayer1.Stop;
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
AndroidTTS1.setSpeechRate(0.75);
AndroidTTS1.SpeakBundle('Hello bundle is this method!');
end;
procedure TForm1.SpeechRecognition1Command(Sender: TObject; Guess: string);
begin
memo1.Lines.Add('Command: ' + Guess);
end;
procedure TForm1.SpeechRecognition1Recognition(Sender: TObject; Guess: string);
begin
memo1.Lines.Add('OnRecognition: ' + Guess);
end;
end.
On line 123 in https://github.com/jimmckeeth/FireMonkey-Android-Voice/blob/master/JNIBridge/Androidapi.JNI.TTS.pas .... I changed it to:
function speak(text: JString; queueMode: Integer; params: JHashMap) : Integer; cdecl; Overload;
function speak(text: JString; queueMode: Integer; params: JBundle; utteranceId: JString) : Integer; cdecl; Overload;
On line 44 in https://github.com/jimmckeeth/FireMonkey-Android-Voice/blob/master/Components/AndroidTTS.pas ... I changed it to:
procedure Speak(say: String);
procedure SpeakBundle(say: String);
procedure SpeakVolume(say: String; volume: single);
procedure setPitch(pitch: Single);
procedure setSpeechRate(speechRate: Single);
function isSpeaking: Boolean;
On line 80 to 97 in https://github.com/jimmckeeth/FireMonkey-Android-Voice/blob/master/Components/AndroidTTS.pas ... I changed it to this:
procedure TAndroidTTS.SpeakBundle(say: String);
{$IFDEF ANDROID}
var
params: JBundle;
begin
params := nil;
//params := TJHashMap.Create;
//params.put(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_VOLUME,
//StringToJString('0.75'));
//params := TJBundle.JavaClass.init();
//params.putFloat(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_VOLUME,volume);
// This needs to be a <String,String> hashmap for the OnDone to work.
{ params := TJHashMap.JavaClass.init();
params.put(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_UTTERANCE_ID,
StringToJString('id')); }
ftts.speak(StringToJString(say), TJTextToSpeech.JavaClass.QUEUE_FLUSH, params, StringToJString('1'));
end;
{$ELSE}
begin
end;
{$ENDIF}
procedure TAndroidTTS.SpeakVolume(say: String; volume: single);
{$IFDEF ANDROID}
var
params: JBundle;
begin
//params := TJHashMap.Create;
//params.put(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_VOLUME,
//StringToJString('0.75'));
//params := nil;
params := TJBundle.JavaClass.init();
params.putFloat(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_VOLUME,0.75);
// This needs to be a <String,String> hashmap for the OnDone to work.
{ params := TJHashMap.JavaClass.init();
params.put(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_UTTERANCE_ID,
StringToJString('id')); }
//ftts.speak(StringToJString(say), TJTextToSpeech.JavaClass.QUEUE_ADD, params, StringToJString('1'));
end;
{$ELSE}
begin
end;
{$ENDIF}
procedure TAndroidTTS.setPitch(pitch: Single);
{$IFDEF ANDROID}
begin
//pitch float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it.
ftts.setPitch(pitch);
end;
{$ELSE}
begin
end;
{$ENDIF}
procedure TAndroidTTS.setSpeechRate(speechRate: Single);
{$IFDEF ANDROID}
begin
//float: Speech rate. 1.0 is the normal speech rate, lower values slow down the speech (0.5 is half the normal speech rate), greater values accelerate it (2.0 is twice the normal speech rate).
ftts.setSpeechRate(speechRate);
end;
{$ELSE}
begin
end;
{$ENDIF}
function TAndroidTTS.isSpeaking: Boolean;
{$IFDEF ANDROID}
begin
// Checks whether the TTS engine is busy speaking.
result := ftts.isSpeaking;
end;
{$ELSE}
begin
result := false;
end;
{$ENDIF}
I can't seem to get the procedure TAndroidTTS.SpeakVolume(say: String; volume: single); to work correctly. Android gives me an error "External exception 1." on the line with "params.putFloat(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_VOLUME,0.75);"
I changed and updated the code along with https://developer.android.com/reference/android/speech/tts/TextToSpeech.Engine#KEY_PARAM_VOLUME because they changed it to using Bundles instead of Hashmaps.
I'm not entirely sure what is wrong with params.putFloat(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_VOLUME,0.75); ... this is where it Android stops and gives the code.
Other resources:
I've looked at which is why my code uses params.putFloat:
Android text to speech volume not changing
The only thing I could find on my error with Delphi is this:
https://community.idera.com/developer-tools/platforms/f/android-platform/70741/inapppurchase
which should be fixed in Delphi 10.4.1, right? I checked the https://quality.embarcadero.com/browse/RSP-27140 webpage to find that was fixed. Am I having a different issue?
Any help much appreciated!
The exception you're receiving is because this import has an error:
https://github.com/jimmckeeth/FireMonkey-Android-Voice/blob/master/JNIBridge/Androidapi.JNI.TTS.pas
..because JTextToSpeech_Engine
has no JavaSignature
attribute. It should be:
[JavaSignature('android/speech/tts/TextToSpeech$Engine')]
JTextToSpeech_Engine = interface(JObject)
['{5BAC3048-CB0C-4DC4-AF62-D0D9AE4394CF}']
end;
Reference:
The declarations for the speak method should be:
function speak(text: JCharSequence; queueMode: Integer; params: JBundle; utteranceId: JString): Integer; cdecl; overload;
function speak(text: JString; queueMode: Integer; params: JHashMap): Integer; deprecated; cdecl; overload;
Call it this way:
ftts.speak(StrToJCharSequence(say), TJTextToSpeech.JavaClass.QUEUE_ADD, params, StringToJString('1'));