androiddelphitextvolumespeech

Using Delphi, I'm trying to change the volume of the AndroidTTS, but I seem to keep getting the error "External exeception 1"


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!


Solution

  • 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:

    https://developer.android.com/reference/android/speech/tts/TextToSpeech#speak(java.lang.CharSequence,%20int,%20android.os.Bundle,%20java.lang.String)

    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'));