Add Games File

This commit is contained in:
Ivan Anikin
2026-05-19 13:42:41 +07:00
parent 7908479e0c
commit 12e7e22c1a
3672 changed files with 125922 additions and 0 deletions
@@ -0,0 +1,19 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0.0",
"FriendlyName": "Android Speech To Text",
"Description": "Blueprint Android speech recognition using the native SpeechRecognizer API.",
"Category": "Input",
"CreatedBy": "FunnyGirl",
"CanContainContent": false,
"IsBetaVersion": true,
"EnabledByDefault": true,
"Modules": [
{
"Name": "AndroidSpeechToText",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:android="http://schemas.android.com/apk/res/android">
<init>
<log text="AndroidSpeechToText init"/>
</init>
<androidManifestUpdates>
<addPermission android:name="android.permission.RECORD_AUDIO"/>
</androidManifestUpdates>
<prebuildCopies>
<log text="Copying AndroidSpeechToText Java source"/>
<copyFile src="$S(PluginDir)/Java/AndroidSpeechToText.java" dst="$S(BuildDir)/src/com/epicgames/unreal/AndroidSpeechToText.java"/>
</prebuildCopies>
<proguardAdditions>
<insert>
-keep class com.epicgames.unreal.AndroidSpeechToText {
public *;
}
</insert>
</proguardAdditions>
<gameActivityOnDestroyAdditions>
<insert>
AndroidSpeechToText.destroyRecognizer();
</insert>
</gameActivityOnDestroyAdditions>
<gameActivityOnRequestPermissionsResultAdditions>
<insert>
AndroidSpeechToText.handlePermissionResult(this, requestCode, permissions, grantResults);
</insert>
</gameActivityOnRequestPermissionsResultAdditions>
</root>
@@ -0,0 +1,389 @@
package com.epicgames.unreal;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Locale;
public class AndroidSpeechToText {
private static final int REQUEST_RECORD_AUDIO = 52031;
private static final long RESTART_DELAY_MS = 250;
private static SpeechRecognizer recognizer = null;
private static boolean isListening = false;
private static boolean enablePartialResults = true;
private static boolean keepListening = false;
private static boolean recognizerActive = false;
private static boolean hasPendingPermissionStart = false;
private static String pendingLanguageTag = "";
private static boolean pendingPreferOffline = false;
private static boolean pendingPartialResults = true;
private static boolean pendingKeepListening = true;
private static String activeLanguageTag = "";
private static boolean activePreferOffline = false;
private static boolean activePartialResults = true;
private static WeakReference<Activity> activeActivity = new WeakReference<Activity>(null);
private static String lastFinalText = "";
private static String lastPartialText = "";
private static String lastErrorMessage = "";
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
public static native void nativeOnReady();
public static native void nativeOnBeginningOfSpeech();
public static native void nativeOnEndOfSpeech();
public static native void nativeOnPartialText(String text);
public static native void nativeOnFinalText(String text);
public static native void nativeOnError(int errorCode, String message);
public static boolean startListening(final Activity activity, final String languageTag, final boolean preferOffline, final boolean partialResults, final boolean shouldKeepListening) {
if (activity == null) {
keepListening = false;
setError(SpeechRecognizer.ERROR_CLIENT, "Android activity is not available.");
return false;
}
if (!SpeechRecognizer.isRecognitionAvailable(activity)) {
keepListening = false;
setError(1000, "Speech recognition is not available on this device.");
return false;
}
if (!hasRecordAudioPermission(activity)) {
hasPendingPermissionStart = true;
pendingLanguageTag = languageTag != null ? languageTag : "";
pendingPreferOffline = preferOffline;
pendingPartialResults = partialResults;
pendingKeepListening = shouldKeepListening;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO);
}
setError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS, "Microphone permission is required.");
return false;
}
enablePartialResults = partialResults;
keepListening = shouldKeepListening;
activeLanguageTag = languageTag != null ? languageTag : "";
activePreferOffline = preferOffline;
activePartialResults = partialResults;
activeActivity = new WeakReference<Activity>(activity);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
ensureRecognizer(activity);
lastFinalText = "";
lastPartialText = "";
lastErrorMessage = "";
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, partialResults);
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, activity.getPackageName());
intent.putExtra(RecognizerIntent.EXTRA_PREFER_OFFLINE, preferOffline);
if (languageTag != null && languageTag.length() > 0) {
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, languageTag);
} else {
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault().toLanguageTag());
}
isListening = true;
recognizerActive = true;
recognizer.startListening(intent);
} catch (Exception exception) {
isListening = false;
recognizerActive = false;
setError(SpeechRecognizer.ERROR_CLIENT, exception.getMessage());
}
}
});
return true;
}
public static void stopListening() {
keepListening = false;
if (recognizer == null) {
isListening = false;
recognizerActive = false;
return;
}
runRecognizerCommand(new Runnable() {
@Override
public void run() {
try {
recognizer.stopListening();
} catch (Exception exception) {
isListening = false;
recognizerActive = false;
setError(SpeechRecognizer.ERROR_CLIENT, exception.getMessage());
}
}
});
}
public static void cancelListening() {
keepListening = false;
if (recognizer == null) {
isListening = false;
recognizerActive = false;
return;
}
runRecognizerCommand(new Runnable() {
@Override
public void run() {
try {
recognizer.cancel();
} catch (Exception exception) {
setError(SpeechRecognizer.ERROR_CLIENT, exception.getMessage());
}
isListening = false;
recognizerActive = false;
}
});
}
public static void destroyRecognizer() {
keepListening = false;
if (recognizer == null) {
isListening = false;
recognizerActive = false;
return;
}
runRecognizerCommand(new Runnable() {
@Override
public void run() {
try {
recognizer.destroy();
} catch (Exception ignored) {
}
recognizer = null;
isListening = false;
recognizerActive = false;
}
});
}
public static boolean isListening() {
return isListening || keepListening;
}
public static boolean isSpeechRecognizerAvailable(Activity activity) {
return activity != null && SpeechRecognizer.isRecognitionAvailable(activity);
}
public static void handlePermissionResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) {
if (requestCode != REQUEST_RECORD_AUDIO || !hasPendingPermissionStart) {
return;
}
boolean granted = grantResults != null
&& grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (!granted) {
hasPendingPermissionStart = false;
setError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS, "Microphone permission was denied.");
return;
}
String languageTag = pendingLanguageTag;
boolean preferOffline = pendingPreferOffline;
boolean partialResults = pendingPartialResults;
boolean shouldKeepListening = pendingKeepListening;
hasPendingPermissionStart = false;
startListening(activity, languageTag, preferOffline, partialResults, shouldKeepListening);
}
public static String getLastFinalText() {
return lastFinalText;
}
public static String getLastPartialText() {
return lastPartialText;
}
public static String getLastErrorMessage() {
return lastErrorMessage;
}
private static void ensureRecognizer(Activity activity) {
if (recognizer != null) {
recognizer.cancel();
recognizer.destroy();
}
recognizer = SpeechRecognizer.createSpeechRecognizer(activity.getApplicationContext());
recognizer.setRecognitionListener(new RecognitionListener() {
@Override
public void onReadyForSpeech(Bundle params) {
nativeOnReady();
}
@Override
public void onBeginningOfSpeech() {
nativeOnBeginningOfSpeech();
}
@Override
public void onRmsChanged(float rmsdB) {
}
@Override
public void onBufferReceived(byte[] buffer) {
}
@Override
public void onEndOfSpeech() {
recognizerActive = false;
isListening = keepListening;
nativeOnEndOfSpeech();
}
@Override
public void onError(int error) {
recognizerActive = false;
isListening = keepListening;
setError(error, errorToMessage(error));
if (shouldRestartAfterError(error)) {
scheduleRestart();
}
}
@Override
public void onResults(Bundle results) {
recognizerActive = false;
isListening = keepListening;
String text = firstRecognitionResult(results);
lastFinalText = text;
nativeOnFinalText(text);
scheduleRestart();
}
@Override
public void onPartialResults(Bundle partialResults) {
if (!enablePartialResults) {
return;
}
String text = firstRecognitionResult(partialResults);
lastPartialText = text;
nativeOnPartialText(text);
}
@Override
public void onEvent(int eventType, Bundle params) {
}
});
}
private static String firstRecognitionResult(Bundle results) {
if (results == null) {
return "";
}
ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (matches == null || matches.isEmpty()) {
return "";
}
String first = matches.get(0);
return first != null ? first : "";
}
private static boolean hasRecordAudioPermission(Activity activity) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|| activity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
}
private static void runRecognizerCommand(Runnable command) {
mainHandler.post(command);
}
private static void scheduleRestart() {
if (!keepListening) {
return;
}
mainHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (!keepListening || recognizerActive) {
return;
}
Activity activity = activeActivity.get();
if (activity == null) {
keepListening = false;
isListening = false;
setError(SpeechRecognizer.ERROR_CLIENT, "Android activity is not available.");
return;
}
startListening(activity, activeLanguageTag, activePreferOffline, activePartialResults, true);
}
}, RESTART_DELAY_MS);
}
private static boolean shouldRestartAfterError(int error) {
return keepListening
&& error != SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS
&& error != SpeechRecognizer.ERROR_CLIENT
&& error != 1000;
}
private static void setError(int errorCode, String message) {
lastErrorMessage = message != null ? message : errorToMessage(errorCode);
nativeOnError(errorCode, lastErrorMessage);
}
private static String errorToMessage(int error) {
switch (error) {
case SpeechRecognizer.ERROR_NETWORK_TIMEOUT:
return "Network timeout.";
case SpeechRecognizer.ERROR_NETWORK:
return "Network error.";
case SpeechRecognizer.ERROR_AUDIO:
return "Audio recording error.";
case SpeechRecognizer.ERROR_SERVER:
return "Recognition server error.";
case SpeechRecognizer.ERROR_CLIENT:
return "Recognition client error.";
case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
return "No speech input.";
case SpeechRecognizer.ERROR_NO_MATCH:
return "Speech was not recognized.";
case SpeechRecognizer.ERROR_RECOGNIZER_BUSY:
return "Speech recognizer is busy.";
case SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS:
return "Insufficient microphone permissions.";
case 10:
return "Too many recognition requests.";
case 11:
return "Recognition server disconnected.";
case 12:
return "Language is not supported.";
case 13:
return "Language is unavailable.";
case 1000:
return "Speech recognition is not available on this device.";
default:
return "Unknown speech recognition error: " + error;
}
}
}
@@ -0,0 +1,34 @@
using System.IO;
using UnrealBuildTool;
public class AndroidSpeechToText : ModuleRules
{
public AndroidSpeechToText(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine"
});
PrivateDependencyModuleNames.AddRange(new string[]
{
"Projects"
});
if (Target.Platform == UnrealTargetPlatform.Android)
{
PrivateDependencyModuleNames.AddRange(new string[]
{
"ApplicationCore",
"Launch"
});
string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "Android", "AndroidSpeechToText_UPL.xml"));
}
}
}
@@ -0,0 +1,140 @@
#include "AndroidSpeechToTextComponent.h"
#include "AndroidSpeechToTextEvents.h"
#include "AndroidSpeechToTextJavaBridge.h"
UAndroidSpeechToTextComponent::UAndroidSpeechToTextComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UAndroidSpeechToTextComponent::BeginPlay()
{
Super::BeginPlay();
AndroidSpeechToTextEvents::OnReady.AddUObject(this, &UAndroidSpeechToTextComponent::HandleSpeechReady);
AndroidSpeechToTextEvents::OnBeginning.AddUObject(this, &UAndroidSpeechToTextComponent::HandleSpeechStarted);
AndroidSpeechToTextEvents::OnEnd.AddUObject(this, &UAndroidSpeechToTextComponent::HandleSpeechEnded);
AndroidSpeechToTextEvents::OnPartialText.AddUObject(this, &UAndroidSpeechToTextComponent::HandlePartialText);
AndroidSpeechToTextEvents::OnFinalText.AddUObject(this, &UAndroidSpeechToTextComponent::HandleFinalText);
AndroidSpeechToTextEvents::OnError.AddUObject(this, &UAndroidSpeechToTextComponent::HandleSpeechError);
}
void UAndroidSpeechToTextComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
AndroidSpeechToTextEvents::OnReady.RemoveAll(this);
AndroidSpeechToTextEvents::OnBeginning.RemoveAll(this);
AndroidSpeechToTextEvents::OnEnd.RemoveAll(this);
AndroidSpeechToTextEvents::OnPartialText.RemoveAll(this);
AndroidSpeechToTextEvents::OnFinalText.RemoveAll(this);
AndroidSpeechToTextEvents::OnError.RemoveAll(this);
Super::EndPlay(EndPlayReason);
}
bool UAndroidSpeechToTextComponent::StartListening(const FString& LanguageTag, bool bPreferOffline, bool bEnablePartialResults, bool bKeepListening)
{
return AndroidSpeechToTextJavaBridge::StartListening(LanguageTag, bPreferOffline, bEnablePartialResults, bKeepListening);
}
void UAndroidSpeechToTextComponent::StopListening()
{
AndroidSpeechToTextJavaBridge::StopListening();
}
void UAndroidSpeechToTextComponent::CancelListening()
{
AndroidSpeechToTextJavaBridge::CancelListening();
}
bool UAndroidSpeechToTextComponent::IsListening() const
{
return AndroidSpeechToTextJavaBridge::IsListening();
}
FString UAndroidSpeechToTextComponent::GetLastFinalText() const
{
return AndroidSpeechToTextJavaBridge::GetLastFinalText();
}
FString UAndroidSpeechToTextComponent::GetLastPartialText() const
{
return AndroidSpeechToTextJavaBridge::GetLastPartialText();
}
FString UAndroidSpeechToTextComponent::GetLastErrorMessage() const
{
return AndroidSpeechToTextJavaBridge::GetLastErrorMessage();
}
bool UAndroidSpeechToTextComponent::IsAndroidSpeechToTextSupported()
{
return AndroidSpeechToTextJavaBridge::IsSupported();
}
void UAndroidSpeechToTextComponent::HandleSpeechReady()
{
OnSpeechReady.Broadcast();
}
void UAndroidSpeechToTextComponent::HandleSpeechStarted()
{
OnSpeechStarted.Broadcast();
}
void UAndroidSpeechToTextComponent::HandleSpeechEnded()
{
OnSpeechEnded.Broadcast();
}
void UAndroidSpeechToTextComponent::HandlePartialText(const FString& Text)
{
OnPartialText.Broadcast(Text);
}
void UAndroidSpeechToTextComponent::HandleFinalText(const FString& Text)
{
OnFinalText.Broadcast(Text);
}
void UAndroidSpeechToTextComponent::HandleSpeechError(int32 ErrorCode, const FString& Message)
{
OnSpeechError.Broadcast(ConvertErrorCode(ErrorCode), Message);
}
EAndroidSpeechToTextError UAndroidSpeechToTextComponent::ConvertErrorCode(int32 ErrorCode)
{
switch (ErrorCode)
{
case 1:
return EAndroidSpeechToTextError::NetworkTimeout;
case 2:
return EAndroidSpeechToTextError::Network;
case 3:
return EAndroidSpeechToTextError::Audio;
case 4:
return EAndroidSpeechToTextError::Server;
case 5:
return EAndroidSpeechToTextError::Client;
case 6:
return EAndroidSpeechToTextError::SpeechTimeout;
case 7:
return EAndroidSpeechToTextError::NoMatch;
case 8:
return EAndroidSpeechToTextError::RecognizerBusy;
case 9:
return EAndroidSpeechToTextError::InsufficientPermissions;
case 10:
return EAndroidSpeechToTextError::TooManyRequests;
case 11:
return EAndroidSpeechToTextError::ServerDisconnected;
case 12:
return EAndroidSpeechToTextError::LanguageNotSupported;
case 13:
return EAndroidSpeechToTextError::LanguageUnavailable;
case 1000:
return EAndroidSpeechToTextError::Unsupported;
default:
return EAndroidSpeechToTextError::Unknown;
}
}
@@ -0,0 +1,17 @@
#pragma once
#include "CoreMinimal.h"
DECLARE_MULTICAST_DELEGATE(FAndroidSpeechToTextNativeSimpleDelegate);
DECLARE_MULTICAST_DELEGATE_OneParam(FAndroidSpeechToTextNativeTextDelegate, const FString&);
DECLARE_MULTICAST_DELEGATE_TwoParams(FAndroidSpeechToTextNativeErrorDelegate, int32, const FString&);
namespace AndroidSpeechToTextEvents
{
extern FAndroidSpeechToTextNativeSimpleDelegate OnReady;
extern FAndroidSpeechToTextNativeSimpleDelegate OnBeginning;
extern FAndroidSpeechToTextNativeSimpleDelegate OnEnd;
extern FAndroidSpeechToTextNativeTextDelegate OnPartialText;
extern FAndroidSpeechToTextNativeTextDelegate OnFinalText;
extern FAndroidSpeechToTextNativeErrorDelegate OnError;
}
@@ -0,0 +1,239 @@
#include "AndroidSpeechToTextJavaBridge.h"
#include "AndroidSpeechToTextEvents.h"
#include "Async/Async.h"
#if PLATFORM_ANDROID
#include "Android/AndroidApplication.h"
#include "Android/AndroidJavaEnv.h"
#include "Android/AndroidJNI.h"
#endif
namespace AndroidSpeechToTextEvents
{
FAndroidSpeechToTextNativeSimpleDelegate OnReady;
FAndroidSpeechToTextNativeSimpleDelegate OnBeginning;
FAndroidSpeechToTextNativeSimpleDelegate OnEnd;
FAndroidSpeechToTextNativeTextDelegate OnPartialText;
FAndroidSpeechToTextNativeTextDelegate OnFinalText;
FAndroidSpeechToTextNativeErrorDelegate OnError;
}
namespace
{
#if PLATFORM_ANDROID
jclass GetJavaClass(JNIEnv* Env)
{
static jclass SpeechClass = FAndroidApplication::FindJavaClassGlobalRef("com/epicgames/unreal/AndroidSpeechToText");
if (SpeechClass == nullptr)
{
FAndroidApplication::CheckJavaException();
UE_LOG(LogTemp, Error, TEXT("AndroidSpeechToText Java class was not found."));
}
return SpeechClass;
}
jmethodID GetStaticMethod(JNIEnv* Env, const ANSICHAR* MethodName, const ANSICHAR* Signature)
{
jclass SpeechClass = GetJavaClass(Env);
if (SpeechClass == nullptr)
{
return nullptr;
}
jmethodID Method = FJavaWrapper::FindStaticMethod(Env, SpeechClass, MethodName, Signature, false);
if (Method == nullptr)
{
FAndroidApplication::CheckJavaException();
UE_LOG(LogTemp, Error, TEXT("AndroidSpeechToText Java method was not found: %s"), ANSI_TO_TCHAR(MethodName));
}
return Method;
}
FString JavaStringResult(const ANSICHAR* MethodName)
{
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass SpeechClass = Env ? GetJavaClass(Env) : nullptr;
jmethodID Method = Env ? GetStaticMethod(Env, MethodName, "()Ljava/lang/String;") : nullptr;
if (SpeechClass != nullptr && Method != nullptr)
{
jstring Result = static_cast<jstring>(Env->CallStaticObjectMethod(SpeechClass, Method));
FAndroidApplication::CheckJavaException();
return FJavaHelper::FStringFromLocalRef(Env, Result);
}
return FString();
}
#endif
void BroadcastSimpleOnGameThread(FAndroidSpeechToTextNativeSimpleDelegate& Delegate)
{
AsyncTask(ENamedThreads::GameThread, [&Delegate]()
{
Delegate.Broadcast();
});
}
void BroadcastTextOnGameThread(FAndroidSpeechToTextNativeTextDelegate& Delegate, FString Text)
{
AsyncTask(ENamedThreads::GameThread, [&Delegate, Text]()
{
Delegate.Broadcast(Text);
});
}
}
namespace AndroidSpeechToTextJavaBridge
{
bool StartListening(const FString& LanguageTag, bool bPreferOffline, bool bEnablePartialResults, bool bKeepListening)
{
#if PLATFORM_ANDROID
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass SpeechClass = Env ? GetJavaClass(Env) : nullptr;
jmethodID Method = Env ? GetStaticMethod(Env, "startListening", "(Landroid/app/Activity;Ljava/lang/String;ZZZ)Z") : nullptr;
if (SpeechClass == nullptr || Method == nullptr)
{
return false;
}
auto JavaLanguageTag = FJavaHelper::ToJavaString(Env, LanguageTag);
const jboolean Result = Env->CallStaticBooleanMethod(
SpeechClass,
Method,
FJavaWrapper::GameActivityThis,
*JavaLanguageTag,
bPreferOffline ? JNI_TRUE : JNI_FALSE,
bEnablePartialResults ? JNI_TRUE : JNI_FALSE,
bKeepListening ? JNI_TRUE : JNI_FALSE);
FAndroidApplication::CheckJavaException();
return Result == JNI_TRUE;
#else
return false;
#endif
}
void StopListening()
{
#if PLATFORM_ANDROID
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass SpeechClass = Env ? GetJavaClass(Env) : nullptr;
jmethodID Method = Env ? GetStaticMethod(Env, "stopListening", "()V") : nullptr;
if (SpeechClass != nullptr && Method != nullptr)
{
Env->CallStaticVoidMethod(SpeechClass, Method);
FAndroidApplication::CheckJavaException();
}
#endif
}
void CancelListening()
{
#if PLATFORM_ANDROID
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass SpeechClass = Env ? GetJavaClass(Env) : nullptr;
jmethodID Method = Env ? GetStaticMethod(Env, "cancelListening", "()V") : nullptr;
if (SpeechClass != nullptr && Method != nullptr)
{
Env->CallStaticVoidMethod(SpeechClass, Method);
FAndroidApplication::CheckJavaException();
}
#endif
}
bool IsListening()
{
#if PLATFORM_ANDROID
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass SpeechClass = Env ? GetJavaClass(Env) : nullptr;
jmethodID Method = Env ? GetStaticMethod(Env, "isListening", "()Z") : nullptr;
if (SpeechClass != nullptr && Method != nullptr)
{
const jboolean Result = Env->CallStaticBooleanMethod(SpeechClass, Method);
FAndroidApplication::CheckJavaException();
return Result == JNI_TRUE;
}
#endif
return false;
}
FString GetLastFinalText()
{
#if PLATFORM_ANDROID
return JavaStringResult("getLastFinalText");
#else
return FString();
#endif
}
FString GetLastPartialText()
{
#if PLATFORM_ANDROID
return JavaStringResult("getLastPartialText");
#else
return FString();
#endif
}
FString GetLastErrorMessage()
{
#if PLATFORM_ANDROID
return JavaStringResult("getLastErrorMessage");
#else
return FString();
#endif
}
bool IsSupported()
{
#if PLATFORM_ANDROID
JNIEnv* Env = FAndroidApplication::GetJavaEnv();
jclass SpeechClass = Env ? GetJavaClass(Env) : nullptr;
jmethodID Method = Env ? GetStaticMethod(Env, "isSpeechRecognizerAvailable", "(Landroid/app/Activity;)Z") : nullptr;
if (SpeechClass != nullptr && Method != nullptr)
{
const jboolean Result = Env->CallStaticBooleanMethod(SpeechClass, Method, FJavaWrapper::GameActivityThis);
FAndroidApplication::CheckJavaException();
return Result == JNI_TRUE;
}
#endif
return false;
}
}
#if PLATFORM_ANDROID
extern "C"
{
JNIEXPORT void JNICALL Java_com_epicgames_unreal_AndroidSpeechToText_nativeOnReady(JNIEnv* Env, jclass Class)
{
BroadcastSimpleOnGameThread(AndroidSpeechToTextEvents::OnReady);
}
JNIEXPORT void JNICALL Java_com_epicgames_unreal_AndroidSpeechToText_nativeOnBeginningOfSpeech(JNIEnv* Env, jclass Class)
{
BroadcastSimpleOnGameThread(AndroidSpeechToTextEvents::OnBeginning);
}
JNIEXPORT void JNICALL Java_com_epicgames_unreal_AndroidSpeechToText_nativeOnEndOfSpeech(JNIEnv* Env, jclass Class)
{
BroadcastSimpleOnGameThread(AndroidSpeechToTextEvents::OnEnd);
}
JNIEXPORT void JNICALL Java_com_epicgames_unreal_AndroidSpeechToText_nativeOnPartialText(JNIEnv* Env, jclass Class, jstring Text)
{
BroadcastTextOnGameThread(AndroidSpeechToTextEvents::OnPartialText, FJavaHelper::FStringFromParam(Env, Text));
}
JNIEXPORT void JNICALL Java_com_epicgames_unreal_AndroidSpeechToText_nativeOnFinalText(JNIEnv* Env, jclass Class, jstring Text)
{
BroadcastTextOnGameThread(AndroidSpeechToTextEvents::OnFinalText, FJavaHelper::FStringFromParam(Env, Text));
}
JNIEXPORT void JNICALL Java_com_epicgames_unreal_AndroidSpeechToText_nativeOnError(JNIEnv* Env, jclass Class, jint ErrorCode, jstring Message)
{
const FString ErrorMessage = FJavaHelper::FStringFromParam(Env, Message);
AsyncTask(ENamedThreads::GameThread, [ErrorCode = static_cast<int32>(ErrorCode), ErrorMessage]()
{
AndroidSpeechToTextEvents::OnError.Broadcast(ErrorCode, ErrorMessage);
});
}
}
#endif
@@ -0,0 +1,15 @@
#pragma once
#include "CoreMinimal.h"
namespace AndroidSpeechToTextJavaBridge
{
bool StartListening(const FString& LanguageTag, bool bPreferOffline, bool bEnablePartialResults, bool bKeepListening);
void StopListening();
void CancelListening();
bool IsListening();
FString GetLastFinalText();
FString GetLastPartialText();
FString GetLastErrorMessage();
bool IsSupported();
}
@@ -0,0 +1,7 @@
#include "Modules/ModuleManager.h"
class FAndroidSpeechToTextModule : public IModuleInterface
{
};
IMPLEMENT_MODULE(FAndroidSpeechToTextModule, AndroidSpeechToText)
@@ -0,0 +1,94 @@
#pragma once
#include "Components/ActorComponent.h"
#include "AndroidSpeechToTextComponent.generated.h"
UENUM(BlueprintType)
enum class EAndroidSpeechToTextError : uint8
{
None UMETA(DisplayName = "None"),
NetworkTimeout UMETA(DisplayName = "Network Timeout"),
Network UMETA(DisplayName = "Network"),
Audio UMETA(DisplayName = "Audio"),
Server UMETA(DisplayName = "Server"),
Client UMETA(DisplayName = "Client"),
SpeechTimeout UMETA(DisplayName = "Speech Timeout"),
NoMatch UMETA(DisplayName = "No Match"),
RecognizerBusy UMETA(DisplayName = "Recognizer Busy"),
InsufficientPermissions UMETA(DisplayName = "Insufficient Permissions"),
TooManyRequests UMETA(DisplayName = "Too Many Requests"),
ServerDisconnected UMETA(DisplayName = "Server Disconnected"),
LanguageNotSupported UMETA(DisplayName = "Language Not Supported"),
LanguageUnavailable UMETA(DisplayName = "Language Unavailable"),
Unsupported UMETA(DisplayName = "Unsupported"),
Unknown UMETA(DisplayName = "Unknown")
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAndroidSpeechToTextSimpleDelegate);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAndroidSpeechToTextTextDelegate, const FString&, Text);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FAndroidSpeechToTextErrorDelegate, EAndroidSpeechToTextError, Error, const FString&, Message);
UCLASS(ClassGroup = (Android), meta = (BlueprintSpawnableComponent))
class ANDROIDSPEECHTOTEXT_API UAndroidSpeechToTextComponent : public UActorComponent
{
GENERATED_BODY()
public:
UAndroidSpeechToTextComponent();
UPROPERTY(BlueprintAssignable, Category = "Android Speech To Text")
FAndroidSpeechToTextSimpleDelegate OnSpeechReady;
UPROPERTY(BlueprintAssignable, Category = "Android Speech To Text")
FAndroidSpeechToTextSimpleDelegate OnSpeechStarted;
UPROPERTY(BlueprintAssignable, Category = "Android Speech To Text")
FAndroidSpeechToTextSimpleDelegate OnSpeechEnded;
UPROPERTY(BlueprintAssignable, Category = "Android Speech To Text")
FAndroidSpeechToTextTextDelegate OnPartialText;
UPROPERTY(BlueprintAssignable, Category = "Android Speech To Text")
FAndroidSpeechToTextTextDelegate OnFinalText;
UPROPERTY(BlueprintAssignable, Category = "Android Speech To Text")
FAndroidSpeechToTextErrorDelegate OnSpeechError;
UFUNCTION(BlueprintCallable, Category = "Android Speech To Text")
bool StartListening(const FString& LanguageTag = TEXT(""), bool bPreferOffline = false, bool bEnablePartialResults = true, bool bKeepListening = true);
UFUNCTION(BlueprintCallable, Category = "Android Speech To Text")
void StopListening();
UFUNCTION(BlueprintCallable, Category = "Android Speech To Text")
void CancelListening();
UFUNCTION(BlueprintPure, Category = "Android Speech To Text")
bool IsListening() const;
UFUNCTION(BlueprintPure, Category = "Android Speech To Text")
FString GetLastFinalText() const;
UFUNCTION(BlueprintPure, Category = "Android Speech To Text")
FString GetLastPartialText() const;
UFUNCTION(BlueprintPure, Category = "Android Speech To Text")
FString GetLastErrorMessage() const;
UFUNCTION(BlueprintPure, Category = "Android Speech To Text")
static bool IsAndroidSpeechToTextSupported();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
void HandleSpeechReady();
void HandleSpeechStarted();
void HandleSpeechEnded();
void HandlePartialText(const FString& Text);
void HandleFinalText(const FString& Text);
void HandleSpeechError(int32 ErrorCode, const FString& Message);
static EAndroidSpeechToTextError ConvertErrorCode(int32 ErrorCode);
};