diff --git a/Unreal/Config/DefaultGame.ini b/Unreal/Config/DefaultGame.ini index 4d0a78a..eb73db6 100644 --- a/Unreal/Config/DefaultGame.ini +++ b/Unreal/Config/DefaultGame.ini @@ -6,7 +6,7 @@ CommonButtonAcceptKeyHandling=TriggerClick [/Script/EngineSettings.GeneralProjectSettings] ProjectID=4B0928DF4291E6F7F4F0D2BD9F00EF29 ProjectName=SPIE Avatar -ProjectVersion=0.1.5 +ProjectVersion=0.1.6 [/Script/UnrealEd.ProjectPackagingSettings] Build=IfProjectHasCode diff --git a/Unreal/Content/SPIE/BP/BP_SPIE_Manager_Child.uasset b/Unreal/Content/SPIE/BP/BP_SPIE_Manager_Child.uasset index e423b63..0179030 100644 --- a/Unreal/Content/SPIE/BP/BP_SPIE_Manager_Child.uasset +++ b/Unreal/Content/SPIE/BP/BP_SPIE_Manager_Child.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54331a82cbe824675a849b48877fce3ad864096ea82d9209505d2107e6719258 -size 761243 +oid sha256:f8269813d71f2481e74c713b402c52df9d06fa339ceeeb94d4b78ee9294c499c +size 763269 diff --git a/Unreal/Content/SPIE/BP/Mode/DA_Mode_SPIE_SpieInnovationDay.uasset b/Unreal/Content/SPIE/BP/Mode/DA_Mode_SPIE_SpieInnovationDay.uasset index ee1765c..5be0e5d 100644 --- a/Unreal/Content/SPIE/BP/Mode/DA_Mode_SPIE_SpieInnovationDay.uasset +++ b/Unreal/Content/SPIE/BP/Mode/DA_Mode_SPIE_SpieInnovationDay.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b8d98fa00c5077a61db502c31e8e53449d75cd39aefb7bb848e3a02e456fb19 -size 6512 +oid sha256:355abbc862fed30bb427b9a3841e86ca393573111716c077afbcaba74e78b093 +size 6662 diff --git a/Unreal/Content/SPIE/Maps/M_SPIE_Startup.umap b/Unreal/Content/SPIE/Maps/M_SPIE_Startup.umap index 3f97748..03b86ba 100644 --- a/Unreal/Content/SPIE/Maps/M_SPIE_Startup.umap +++ b/Unreal/Content/SPIE/Maps/M_SPIE_Startup.umap @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1506d3a1e3a54c28faacccc8f8cfd10487b29b5bd467176bd6f3503a8802dfbe -size 178094 +oid sha256:9f68f7b71d6c5f93caff658629fbee456cc45de65f7402c3b50b5c85b7436ed1 +size 177896 diff --git a/Unreal/Plugins/AvatarCore_Manager/Content/AvatarCoreManager.uasset b/Unreal/Plugins/AvatarCore_Manager/Content/AvatarCoreManager.uasset index 743c8f8..aae9437 100644 --- a/Unreal/Plugins/AvatarCore_Manager/Content/AvatarCoreManager.uasset +++ b/Unreal/Plugins/AvatarCore_Manager/Content/AvatarCoreManager.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e422a7b4a9e43fceda48d3d4da309c0c8a086e3f7dc8ed452a485d293a74dbc3 -size 1850919 +oid sha256:3ed3eaf41de388f7ab74e5b3b678477c3926b3cc20322c5b0aa865395b18591c +size 1847459 diff --git a/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugAvatarCoreSTT.uasset b/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugAvatarCoreSTT.uasset index 5eaf711..416892d 100644 --- a/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugAvatarCoreSTT.uasset +++ b/Unreal/Plugins/AvatarCore_Manager/Content/Widgets/Debug/Pages/W_DebugAvatarCoreSTT.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ecc79fa6dbed83f2325e9258c4ad477a4e8650f406f1e66660c4a4678dda8b2 -size 414354 +oid sha256:014c739ef9b6948f33d2096e58451a44d851acd87614b9bb23804037689a7a68 +size 411484 diff --git a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp index ea3e7a4..a47b3ff 100644 --- a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp +++ b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/AzureRunnable.cpp @@ -20,10 +20,8 @@ FAzureRunnable::~FAzureRunnable() { if (Thread) { - Owner = nullptr; - bIsRunning = false; // Unblock the Run() loop before killing the thread - Thread->Kill(true); delete Thread; + Thread = nullptr; } } @@ -66,19 +64,20 @@ bool FAzureRunnable::Init() } TWeakObjectPtr WeakOwner(Owner); + FAzureRunnable* Self = this; // Bind Recognizing event to forward data to the game thread - Recognizer->Recognizing.Connect([WeakOwner](const auto& EventArgs) { + Recognizer->Recognizing.Connect([WeakOwner, Self](const auto& EventArgs) { FString RecognizedText = UTF8_TO_TCHAR(EventArgs.Result->Text.c_str()); - AsyncTask(ENamedThreads::GameThread, [WeakOwner, RecognizedText]() { + AsyncTask(ENamedThreads::GameThread, [WeakOwner, RecognizedText, Self]() { if (WeakOwner.IsValid() && WeakOwner.Get() != nullptr) { - WeakOwner->OnRecognizing(RecognizedText); + WeakOwner->OnRecognizing(RecognizedText, Self); } }); }); // Bind Recognized event for final results - Recognizer->Recognized.Connect([WeakOwner](const auto& EventArgs) { + Recognizer->Recognized.Connect([WeakOwner, Self](const auto& EventArgs) { FString RecognizedText = UTF8_TO_TCHAR(EventArgs.Result->Text.c_str()); FString DetectedLangUE = TEXT("LANGUAGE_NOT_DETECTED"); @@ -95,10 +94,10 @@ bool FAzureRunnable::Init() // Keep default LANGUAGE_NOT_DETECTED } - AsyncTask(ENamedThreads::GameThread, [WeakOwner, RecognizedText, DetectedLangUE]() { + AsyncTask(ENamedThreads::GameThread, [WeakOwner, RecognizedText, DetectedLangUE, Self]() { if (WeakOwner.IsValid() && WeakOwner.Get() != nullptr) { - WeakOwner->OnRecognized(RecognizedText, DetectedLangUE); + WeakOwner->OnRecognized(RecognizedText, DetectedLangUE, Self); } }); }); @@ -155,10 +154,11 @@ uint32 FAzureRunnable::Run() Recognizer->SessionStarted.DisconnectAll(); TWeakObjectPtr WeakOwner(Owner); - AsyncTask(ENamedThreads::GameThread, [WeakOwner]() { + FAzureRunnable* Self = this; + AsyncTask(ENamedThreads::GameThread, [WeakOwner, Self]() { if (WeakOwner.IsValid() && WeakOwner.Get() != nullptr) { - WeakOwner->OnRunnableEnded(); + WeakOwner->OnRunnableEnded(Self); } }); diff --git a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp index 3fb2a71..b88d9ca 100644 --- a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp +++ b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Private/Processor/Azure/STTProcessorAzure.cpp @@ -112,15 +112,15 @@ void USTTProcessorAzure::OnSpeechStateChanged(ESTTTalkingState TalkingState) void USTTProcessorAzure::StartRecognition() { - StopRecognition(true); //In case there is something else running - // Force-destroy any leftover runnable from a non-forced stop - if (AzureRunnable) { - AzureRunnable->StopRecognition(true); - AzureRunnable = nullptr; + StopRecognition(false); // Moves any active runnable to StoppedRunnables + if(AzureRunnable) + { + StoppedRunnables.Add(MoveTemp(AzureRunnable)); // AzureRunnable is now null; object stays alive until Run() finishes } intermediateResult = ""; USTTProcessorBase::OnTranscriptionStarted(); AzureRunnable = MakeUnique(config, audioConfig, STTManager->GetSpecialWords(), this, false); + bTranscriptionRunning = true; } void USTTProcessorAzure::StopRecognition(bool Forced) @@ -129,19 +129,13 @@ void USTTProcessorAzure::StopRecognition(bool Forced) if (AzureRunnable) { AzureRunnable->StopRecognition(Forced); - if (Forced) { - AzureRunnable = nullptr; // Immediate cleanup, no result expected - } - // Non-forced: runnable finishes gracefully and delivers final result if(bDebugMode && STTManager!=nullptr) STTManager->OnSTTLog.Broadcast(TEXT("Recognition thread stopped.")); } } -void USTTProcessorAzure::OnRecognizing(const FString& RecognizedText) +void USTTProcessorAzure::OnRecognizing(const FString& RecognizedText, FAzureRunnable* Caller) { - if (!bTranscriptionRunning) - return; if (!IsValid(STTManager)) return; if (STTManager->IsBlocked()) @@ -156,8 +150,12 @@ void USTTProcessorAzure::OnRecognizing(const FString& RecognizedText) USTTProcessorBase::OnTranscriptionIntermediateResult(TranscriptionCounter, *intermediateResult); } -void USTTProcessorAzure::OnRecognized(const FString& RecognizedText, const FString& Language) +void USTTProcessorAzure::OnRecognized(const FString& RecognizedText, const FString& Language, FAzureRunnable* Caller) { + // Discard callbacks from stopped runnables + if (AzureRunnable == nullptr) { + return; + } if (!IsValid(STTManager)) return; if (STTManager->IsBlocked()) @@ -174,33 +172,55 @@ void USTTProcessorAzure::OnRecognized(const FString& RecognizedText, const FStri } else { USTTProcessorBase::OnTranscriptionResult(TranscriptionCounter, *intermediateResult, this->DetectedLanguage); + if (AzureRunnable) + { + StoppedRunnables.Add(MoveTemp(AzureRunnable)); // AzureRunnable is now null; object stays alive until Run() finishes + } intermediateResult.Empty(); } } void USTTProcessorAzure::OnConnectionSuccess() { + // Connection test runnable returns from Run() before posting this callback, + // so Run() is already done — direct null is safe. AzureRunnable = nullptr; STTManager->OnReady.Broadcast(); STTManager->OnSpeechStateChanged.AddUniqueDynamic(this, &USTTProcessorAzure::OnSpeechStateChanged); - } -void USTTProcessorAzure::OnRunnableEnded() +void USTTProcessorAzure::OnRunnableEnded(FAzureRunnable* Caller) { - bTranscriptionRunning = false; - AzureRunnable = nullptr; + // Check if it's the active runnable + if (AzureRunnable.Get() == Caller) + { + bTranscriptionRunning = false; + AzureRunnable = nullptr; // Safe: Run() has returned - if (IsValid(STTManager)) { - // Send any remaining intermediate result that wasn't finalized by OnRecognized - if (!intermediateResult.IsEmpty()) { - USTTProcessorBase::OnTranscriptionResult(TranscriptionCounter, intermediateResult, DetectedLanguage); - intermediateResult.Empty(); + if (IsValid(STTManager)) { + // Send any remaining intermediate result that wasn't finalized by OnRecognized + if (!intermediateResult.IsEmpty()) { + USTTProcessorBase::OnTranscriptionResult(TranscriptionCounter, intermediateResult, DetectedLanguage); + intermediateResult.Empty(); + } + + if (!STTManager->IsBlocked()) + STTManager->UserSpeechStateChanged(ESTTTalkingState::SILENCE); } - else { - // Ensure we return to SILENCE even if no result was produced - // (empty audio, network timeout, etc.) to prevent stuck TRANSCRIBING state - if(!STTManager->IsBlocked()) + } + else + { + // Caller was a previously stopped runnable — flush its result then remove + StoppedRunnables.RemoveAll([Caller](const TUniquePtr& R) { + return R.Get() == Caller; + }); + if (IsValid(STTManager)) { + if (!intermediateResult.IsEmpty()) { + USTTProcessorBase::OnTranscriptionResult(TranscriptionCounter, intermediateResult, DetectedLanguage); + intermediateResult.Empty(); + } + + if (!STTManager->IsBlocked()) STTManager->UserSpeechStateChanged(ESTTTalkingState::SILENCE); } } @@ -209,9 +229,15 @@ void USTTProcessorAzure::OnRunnableEnded() void USTTProcessorAzure::OnAzureError(FString Error) { bTranscriptionRunning = false; - AzureRunnable = nullptr; intermediateResult.Empty(); + // Remove caller from whichever array owns it + // (Error fires before Run() returns, so we move to StoppedRunnables to keep alive) + if (AzureRunnable) + { + StoppedRunnables.Add(MoveTemp(AzureRunnable)); + } + if (IsValid(STTManager)) { STTManager->OnSTTError.Broadcast(Error); STTManager->UserSpeechStateChanged(ESTTTalkingState::SILENCE); diff --git a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Public/Processor/Azure/STTProcessorAzure.h b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Public/Processor/Azure/STTProcessorAzure.h index 081bf1f..ff4c068 100644 --- a/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Public/Processor/Azure/STTProcessorAzure.h +++ b/Unreal/Plugins/AvatarCore_STT/Source/AvatarCore_STT/Public/Processor/Azure/STTProcessorAzure.h @@ -46,19 +46,19 @@ private: private: TUniquePtr AzureRunnable; + TArray> StoppedRunnables; void StartRecognition(); void StopRecognition(bool Forced); public: - void OnRecognizing(const FString& RecognizedText); - void OnRecognized(const FString& RecognizedText, const FString& Language); + void OnRecognizing(const FString& RecognizedText, FAzureRunnable* Caller); + void OnRecognized(const FString& RecognizedText, const FString& Language, FAzureRunnable* Caller); UFUNCTION() void OnConnectionSuccess(); - UFUNCTION() - void OnRunnableEnded(); + void OnRunnableEnded(FAzureRunnable* Caller); UFUNCTION() void OnAzureError(FString Error);