#pragma once #include "CoreMinimal.h" #include "Engine/GameInstance.h" #if WITH_VIVOXCORE #include "VivoxCore.h" #endif #include "MyGameInstance.generated.h" #define VIVOX_SERVER "YOURAPI" #define VIVOX_ISSUER "INFO" #define VIVOX_DOMAIN "GOES" #define VIVOX_SECRET "HERE" #define TOKEN_EXPIRY (30 * 60) USTRUCT(Blueprintable) struct FVivoxAudioDevice { GENERATED_BODY() UPROPERTY(BlueprintReadOnly) FString Name; UPROPERTY(BlueprintReadOnly) FString UUID; }; enum class EVivoxInit { None = -1, Errors = 0, Warnings = 1, Info = 2, Debug = 3, Trace = 4, All = 5 }; UCLASS() class VIVOXEXAMPLE_API UMyGameInstance : public UGameInstance { GENERATED_BODY() static bool s_bEnableVivox; public: UMyGameInstance(); virtual void Init() override; virtual void StartGameInstance() override; #if WITH_VIVOXCORE VivoxCoreError JoinVoiceChannels(const uint8 TeamChannelToJoin, const FString& SessionID = ""); #endif void LeaveVoiceChannels(); UFUNCTION(BlueprintPure, Category = "Vivox") bool IsVivoxLoggedIn() const { return m_bVivoxLoggedIn; } UFUNCTION(BlueprintPure, Category = "Vivox") bool IsInVoiceChannel() const { return m_bIsInVoiceChannel; } UFUNCTION(BlueprintPure, Category = "Vivox") FString GetVivoxChannelName() const { return m_VivoxChannelName; } UFUNCTION(BlueprintPure, Category = "Vivox") FString GetVivoxLoggedInPlayerName() const { return m_VivoxLoggedInPlayerName; } UFUNCTION(BlueprintPure, Category = "Vivox") TArray GetConnectedClients(const FString& ChannelName) const; UFUNCTION(BlueprintPure, Category = "Vivox") TArray GetClientsTalking(const FString& ChannelName) const; UFUNCTION(BlueprintCallable, Category = "Vivox") void ToggleMuteClient(const FString& ClientToMute, bool bMute = true); UFUNCTION(BlueprintCallable, Category = "Vivox") void ToggleMuteSelfInput(bool bMute = true); UFUNCTION(BlueprintCallable, Category = "Vivox") void ToggleMuteSelfOutput(bool bMute = true); UFUNCTION(BlueprintPure, Category = "Vivox") TArray GetAudioInputDevices() const; UFUNCTION(BlueprintPure, Category = "Vivox") TArray GetAudioOutputDevices() const; UFUNCTION(BlueprintCallable, Category = "Vivox") void SetAudioInputDevice(const FString& AudioDevice); UFUNCTION(BlueprintCallable, Category = "Vivox") void SetAudioOutputDevice(const FString& AudioDevice); UFUNCTION(BlueprintCallable, Category = "Vivox") void SetAudioInputDeviceVolume(int32 NewVolume = 0); UFUNCTION(BlueprintCallable, Category = "Vivox") void SetAudioOutputDeviceVolume(int32 NewVolume = 0); private: void LogoutVivox(); #if WITH_VIVOXCORE VivoxCoreError InitializeVivox(EVivoxInit logLevel); VivoxCoreError LoginVivox(const FString& PlayerName); void BindLoginSessionHandlers(bool DoBind, ILoginSession& LoginSession); void BindChannelSessionHandlers(bool DoBind, IChannelSession& ChannelSession); /** Vivox Delegates */ void OnLoginSessionStateChanged(LoginState State); void OnChannelParticipantAdded(const IParticipant& Participant); void OnChannelParticipantRemoved(const IParticipant& Participant); void OnChannelParticipantUpdated(const IParticipant& Participant); void OnChannelAudioStateChanged(const IChannelConnectionState& State); void OnChannelTextStateChanged(const IChannelConnectionState& State); void OnChannelTextMessageReceived(const IChannelTextMessage& Message); #endif FString m_VivoxChannelName; FString m_VivoxLoggedInPlayerName; bool m_bIsVivoxInitialized; bool m_bIsVivoxInitializing; bool m_bVivoxLoggedIn; bool m_bIsVivoxLoggingIn; bool m_bIsInVoiceChannel; #if WITH_VIVOXCORE IClient* m_VivoxVoiceClient; AccountId m_LoggedInAccountID; ILoginSession* m_LoginSession; ChannelId m_LastKnownTransmittingChannel; #endif }; // ------------------------------------------------- CPP #include "MyGameInstance.h" #include "Kismet/KismetSystemLibrary.h" bool UMyGameInstance::s_bEnableVivox = true; UMyGameInstance::UMyGameInstance() : m_bIsVivoxInitialized(false), m_bIsVivoxInitializing(false), m_bVivoxLoggedIn(false), m_bIsVivoxLoggingIn(false) #if WITH_VIVOXCORE ,m_VivoxVoiceClient(&static_cast(&FModuleManager::Get().LoadModuleChecked(TEXT("VivoxCore")))->VoiceClient()) #endif { } void UMyGameInstance::Init() { #if WITH_VIVOXCORE if (s_bEnableVivox && !UKismetSystemLibrary::IsDedicatedServer(GetWorld())) { InitializeVivox(EVivoxInit::All); } #endif Super::Init(); } void UMyGameInstance::StartGameInstance() { Super::StartGameInstance(); #if WITH_VIVOXCORE // TODO: If logged in with Steam get the userid etc and pass that to Login instead of our temp name const FString TmpName = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); LoginVivox(FString::Printf(TEXT("%s"), *TmpName)); #endif } #if WITH_VIVOXCORE VivoxCoreError UMyGameInstance::JoinVoiceChannels(const uint8 TeamChannelToJoin, const FString& SessionID) { if (!s_bEnableVivox) return VxErrorNotInitialized; if (!m_bVivoxLoggedIn) { return VxErrorNotLoggedIn; } // Player is already in a channel so let's change that if (m_bIsInVoiceChannel) { LeaveVoiceChannels(); JoinVoiceChannels(TeamChannelToJoin, SessionID); return VxErrorInternalError; // There's no error for being already in a channel so let's just throw an internal error } ensure(!m_VivoxLoggedInPlayerName.IsEmpty()); // Create the channel name for the player to join. If the SessionID is empty we generate a random guid for the channel name so the player is put into a "private" room else put them into the actual team room const FString GeneratedChannelName = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens); m_VivoxChannelName = FString::Printf(TEXT("TN%d_%s"), TeamChannelToJoin, SessionID.IsEmpty() ? *FString::Printf(TEXT("G_%s"), *GeneratedChannelName) : *SessionID); ILoginSession& LoginSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID); // It's perfectly safe to add 3D properties to any channel type (they don't have any effect if the channel type is not Positional) ChannelId Channel = ChannelId(VIVOX_ISSUER, m_VivoxChannelName, VIVOX_DOMAIN, ChannelType::NonPositional, Channel3DProperties(8100, 270, 1.0, EAudioFadeModel::InverseByDistance)); IChannelSession& ChannelSession = LoginSession.GetChannelSession(Channel); // This is insecure and should be done via game server const FString JoinToken = ChannelSession.GetConnectToken(VIVOX_SECRET, FTimespan::FromSeconds(TOKEN_EXPIRY)); IChannelSession::FOnBeginConnectCompletedDelegate OnBeginConnectCompleteCallback; OnBeginConnectCompleteCallback.BindLambda([this, &LoginSession, &ChannelSession](VivoxCoreError Status) { if (VxErrorSuccess != Status) { BindChannelSessionHandlers(false, ChannelSession); LoginSession.DeleteChannelSession(ChannelSession.Channel()); m_bIsInVoiceChannel = false; } else { LoginSession.SetTransmissionMode(TransmissionMode::Single); BindChannelSessionHandlers(true, ChannelSession); m_bIsInVoiceChannel = true; } }); m_LastKnownTransmittingChannel = Channel; return ChannelSession.BeginConnect(true, false, true, JoinToken, OnBeginConnectCompleteCallback); } #endif void UMyGameInstance::LeaveVoiceChannels() { #if WITH_VIVOXCORE if (!s_bEnableVivox || !m_bVivoxLoggedIn) return; TArray ChannelSessionKeys; m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID).ChannelSessions().GenerateKeyArray(ChannelSessionKeys); for (const ChannelId& SessionKey : ChannelSessionKeys) { ILoginSession& LoginSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID); IChannelSession& ChannelSession = LoginSession.GetChannelSession(SessionKey); ChannelSession.Disconnect(); BindChannelSessionHandlers(false, ChannelSession); LoginSession.DeleteChannelSession(SessionKey); } m_LastKnownTransmittingChannel = ChannelId(); m_bIsInVoiceChannel = false; #endif } TArray UMyGameInstance::GetConnectedClients(const FString& ChannelName) const { #if WITH_VIVOXCORE if (!s_bEnableVivox) return {}; IChannelSession& ChannelSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID).GetChannelSession(m_LastKnownTransmittingChannel); TArray clients; for (const auto& client : ChannelSession.Participants()) { clients.Add(client.Key); } return clients; #endif return {}; } TArray UMyGameInstance::GetClientsTalking(const FString& ChannelName) const { #if WITH_VIVOXCORE if (!s_bEnableVivox) return {}; TArray clients; IChannelSession& ChannelSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID).GetChannelSession(m_LastKnownTransmittingChannel); for (const auto& client : ChannelSession.Participants()) { if (client.Value->SpeechDetected()) { clients.Add(client.Key); } } return clients; #endif return {}; } void UMyGameInstance::ToggleMuteClient(const FString& ClientToMute, bool bMute /*= true*/) { #if WITH_VIVOXCORE IChannelSession& ChannelSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID).GetChannelSession(m_LastKnownTransmittingChannel); IParticipant* client = *ChannelSession.Participants().Find(ClientToMute); if (client) { client->SetLocalMute(bMute); } #endif } void UMyGameInstance::ToggleMuteSelfInput(bool bMute) { #if WITH_VIVOXCORE m_VivoxVoiceClient->AudioInputDevices().SetMuted(bMute); #endif } void UMyGameInstance::ToggleMuteSelfOutput(bool bMute) { #if WITH_VIVOXCORE m_VivoxVoiceClient->AudioOutputDevices().SetMuted(bMute); #endif } TArray UMyGameInstance::GetAudioInputDevices() const { #if WITH_VIVOXCORE TMap devices = m_VivoxVoiceClient->AudioInputDevices().AvailableDevices(); TArray NewDevices; for (const auto& device : devices) { NewDevices.Add({device.Value->Name(), device.Value->Id()}); } return NewDevices; #endif return {}; } TArray UMyGameInstance::GetAudioOutputDevices() const { #if WITH_VIVOXCORE TMap devices = m_VivoxVoiceClient->AudioOutputDevices().AvailableDevices(); TArray NewDevices; for (const auto& device : devices) { NewDevices.Add({device.Value->Name(), device.Value->Id()}); } return NewDevices; #endif return {}; } void UMyGameInstance::SetAudioInputDevice(const FString& AudioDevice) { #if WITH_VIVOXCORE TMap devices = m_VivoxVoiceClient->AudioInputDevices().AvailableDevices(); m_VivoxVoiceClient->AudioInputDevices().SetActiveDevice(**devices.Find(AudioDevice)); #endif } void UMyGameInstance::SetAudioOutputDevice(const FString& AudioDevice) { #if WITH_VIVOXCORE TMap devices = m_VivoxVoiceClient->AudioOutputDevices().AvailableDevices(); m_VivoxVoiceClient->AudioOutputDevices().SetActiveDevice(**devices.Find(AudioDevice)); #endif } void UMyGameInstance::SetAudioInputDeviceVolume(int32 NewVolume) { #if WITH_VIVOXCORE m_VivoxVoiceClient->AudioInputDevices().SetVolumeAdjustment(FMath::Clamp(NewVolume, -50, 50)); #endif } void UMyGameInstance::SetAudioOutputDeviceVolume(int32 NewVolume) { #if WITH_VIVOXCORE m_VivoxVoiceClient->AudioOutputDevices().SetVolumeAdjustment(FMath::Clamp(NewVolume, -50, 50)); #endif } #if WITH_VIVOXCORE VivoxCoreError UMyGameInstance::InitializeVivox(EVivoxInit logLevel) { if (m_bIsVivoxInitialized || m_bIsVivoxInitializing) { return VxErrorSuccess; } VivoxConfig Config; Config.SetLogLevel((vx_log_level)logLevel); VivoxCoreError Status = m_VivoxVoiceClient->Initialize(Config); if (Status != VxErrorSuccess) { // Something went wrong so we should log } else { m_bIsVivoxInitialized = true; } return Status; } VivoxCoreError UMyGameInstance::LoginVivox(const FString& PlayerName) { if (!s_bEnableVivox) return VxErrorNotInitialized; if (m_bVivoxLoggedIn) { return VxErrorSuccess; } if (m_bIsVivoxLoggingIn) { return VxErrorSuccess; } if (!m_bIsVivoxInitialized) { return VxErrorNotInitialized; } if (m_VivoxVoiceClient == nullptr) { return VxErrorTargetObjectDoesNotExist; } m_VivoxLoggedInPlayerName = PlayerName; m_LoggedInAccountID = AccountId(VIVOX_ISSUER, m_VivoxLoggedInPlayerName, VIVOX_DOMAIN); ILoginSession& LoginSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID); ILoginSession::FOnBeginLoginCompletedDelegate OnBeginLoginCompleteCallback; OnBeginLoginCompleteCallback.BindLambda([this, &LoginSession](VivoxCoreError Status) { m_bIsVivoxLoggingIn = false; if (VxErrorSuccess != Status) { BindLoginSessionHandlers(false, LoginSession); m_LoggedInAccountID = AccountId(); m_VivoxLoggedInPlayerName = FString(); m_bVivoxLoggedIn = false; } else { m_bIsVivoxLoggingIn = true; m_bVivoxLoggedIn = true; BindLoginSessionHandlers(true, LoginSession); } }); const FString LoginToken = LoginSession.GetLoginToken(VIVOX_SECRET, FTimespan::FromSeconds(TOKEN_EXPIRY)); return LoginSession.BeginLogin(VIVOX_SERVER, LoginToken, OnBeginLoginCompleteCallback); } #endif void UMyGameInstance::LogoutVivox() { #if WITH_VIVOXCORE if (!s_bEnableVivox) return; if (!m_bVivoxLoggedIn && !m_bIsVivoxLoggingIn) { return; } ILoginSession& LoginSession = m_VivoxVoiceClient->GetLoginSession(m_LoggedInAccountID); LoginSession.Logout(); m_LoggedInAccountID = AccountId(); m_VivoxLoggedInPlayerName = FString(); m_bIsVivoxLoggingIn = false; m_bVivoxLoggedIn = false; #endif } #if WITH_VIVOXCORE void UMyGameInstance::BindLoginSessionHandlers(bool DoBind, ILoginSession& LoginSession) { if (DoBind) { LoginSession.EventStateChanged.AddUObject(this, &UMyGameInstance::OnLoginSessionStateChanged); } else { LoginSession.EventStateChanged.RemoveAll(this); } } void UMyGameInstance::BindChannelSessionHandlers(bool DoBind, IChannelSession& ChannelSession) { if (DoBind) { ChannelSession.EventAfterParticipantAdded.AddUObject(this, &UMyGameInstance::OnChannelParticipantAdded); ChannelSession.EventBeforeParticipantRemoved.AddUObject(this, &UMyGameInstance::OnChannelParticipantRemoved); ChannelSession.EventAfterParticipantUpdated.AddUObject(this, &UMyGameInstance::OnChannelParticipantUpdated); ChannelSession.EventAudioStateChanged.AddUObject(this, &UMyGameInstance::OnChannelAudioStateChanged); ChannelSession.EventTextStateChanged.AddUObject(this, &UMyGameInstance::OnChannelTextStateChanged); ChannelSession.EventTextMessageReceived.AddUObject(this, &UMyGameInstance::OnChannelTextMessageReceived); } else { ChannelSession.EventAfterParticipantAdded.RemoveAll(this); ChannelSession.EventBeforeParticipantRemoved.RemoveAll(this); ChannelSession.EventAfterParticipantUpdated.RemoveAll(this); ChannelSession.EventAudioStateChanged.RemoveAll(this); ChannelSession.EventTextStateChanged.RemoveAll(this); ChannelSession.EventTextMessageReceived.RemoveAll(this); } } void UMyGameInstance::OnLoginSessionStateChanged(LoginState State) { switch (State) { case LoginState::LoggedOut: m_LoggedInAccountID = AccountId(); m_VivoxLoggedInPlayerName = FString(); m_bIsVivoxLoggingIn = false; m_bVivoxLoggedIn = false; break; } } void UMyGameInstance::OnChannelParticipantAdded(const IParticipant& Participant) { ChannelId Channel = Participant.ParentChannelSession().Channel(); UE_LOG(LogTemp, Log, TEXT("User %s has joined channel %s (self = %s)"), *Participant.Account().Name(), *Channel.Name(), Participant.IsSelf() ? TEXT("true") : TEXT("false")); } void UMyGameInstance::OnChannelParticipantRemoved(const IParticipant& Participant) { } void UMyGameInstance::OnChannelParticipantUpdated(const IParticipant& Participant) { if (Participant.IsSelf()) { UE_LOG(LogTemp, Log, TEXT("Self Participant Updated (audio=%d, text=%d, speaking=%d)"), Participant.InAudio(), Participant.InText(), Participant.SpeechDetected()); } } void UMyGameInstance::OnChannelAudioStateChanged(const IChannelConnectionState& State) { } void UMyGameInstance::OnChannelTextStateChanged(const IChannelConnectionState& State) { } void UMyGameInstance::OnChannelTextMessageReceived(const IChannelTextMessage& Message) { } #endif