diff --git a/Assets/Scripts/EOSVoiceChat.cs b/Assets/Scripts/EOSVoiceChat.cs
new file mode 100644
index 0000000..2fc8341
--- /dev/null
+++ b/Assets/Scripts/EOSVoiceChat.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Epic.OnlineServices;
+using Epic.OnlineServices.Lobby;
+using Epic.OnlineServices.RTC;
+using Epic.OnlineServices.RTCAudio;
+using UnityEngine;
+
+///
+/// Generic re-usable cross-platform class to provide common voice chat functionality based on the EOS Voice Chat service.
+/// This class does not know anything about EOS platform initialization or authentication; it just takes the required
+/// EOS interfaces and exposes a number of game-related voice functions.
+///
+public class EOSVoiceChat
+{
+ private const uint DefaultMaxChatPlayers = 16; // "Lobbies that generate conference rooms must have <= 16 max players"
+
+ private readonly LobbyInterface lobbyInterface;
+ private readonly RTCInterface rtcInterface;
+ private readonly RTCAudioInterface audioInterface;
+ private readonly Func productUserProvider;
+
+ private string connectedLobbyId;
+ public bool IsConnected => lobbyInterface != null && rtcInterface != null && audioInterface != null && !string.IsNullOrEmpty(connectedLobbyId);
+
+ ///
+ /// Provide the required interfaces for voice chat. Product User ID is provided through a callback, so that the
+ /// same instance of this class can remain in use even if the logged in user changes.
+ ///
+ public EOSVoiceChat(
+ LobbyInterface lobbyInterface, RTCInterface rtcInterface, RTCAudioInterface audioInterface,
+ Func productUserProvider)
+ {
+ this.lobbyInterface = lobbyInterface;
+ this.rtcInterface = rtcInterface;
+ this.audioInterface = audioInterface;
+ this.productUserProvider = productUserProvider;
+ }
+
+ ///
+ /// Join an existing chat lobby or create a new one.
+ /// The completion callback is invoked on both success and failure, and can be used to set up the chat lobby's initial parameters.
+ ///
+ public void ConnectToChat(string chatLobbyName, Action onCompleted = null, uint maxChatPlayers = DefaultMaxChatPlayers)
+ {
+ DisconnectChat(); // Leave any currently connected chat lobby
+
+ lobbyInterface.CreateLobbySearch(new CreateLobbySearchOptions { MaxResults = 1 }, out var searchHandle);
+ searchHandle.SetLobbyId(new LobbySearchSetLobbyIdOptions { LobbyId = chatLobbyName });
+
+ var localUserId = productUserProvider.Invoke();
+ searchHandle.Find(new LobbySearchFindOptions { LocalUserId = localUserId }, null, findData =>
+ {
+ switch (findData.ResultCode)
+ {
+ case Result.Success:
+ searchHandle.CopySearchResultByIndex(new LobbySearchCopySearchResultByIndexOptions { LobbyIndex = 0 }, out var lobbyDetails);
+
+ Debug.Log("Found existing lobby, joining...");
+ lobbyInterface.JoinLobby
+ (
+ new JoinLobbyOptions
+ {
+ LocalUserId = localUserId,
+ LobbyDetailsHandle = lobbyDetails,
+ PresenceEnabled = false,
+ },
+ null,
+ data => HandleLobbyJoined(data, onCompleted)
+ );
+ break;
+ default:
+ Debug.Log($"Creating new chat lobby...");
+ lobbyInterface.CreateLobby
+ (
+ new CreateLobbyOptions
+ {
+ LocalUserId = localUserId,
+ AllowInvites = false,
+ PermissionLevel = LobbyPermissionLevel.Publicadvertised,
+ PresenceEnabled = false,
+ MaxLobbyMembers = maxChatPlayers,
+ DisableHostMigration = false,
+ LobbyId = chatLobbyName,
+ BucketId = Application.productName, // TODO: do we need anything more specific than this?
+ EnableRTCRoom = true,
+ },
+ chatLobbyName,
+ data => HandleLobbyCreated(data, onCompleted)
+ );
+ break;
+ }
+ });
+ }
+
+ private void HandleLobbyCreated(CreateLobbyCallbackInfo data, Action onCompleted)
+ {
+ switch (data.ResultCode)
+ {
+ case Result.Success:
+ connectedLobbyId = data.LobbyId;
+ Debug.Log($"Chat lobby creation successful, lobby ID = {connectedLobbyId}");
+ onCompleted?.Invoke(true);
+ break;
+ case Result.LobbyLobbyAlreadyExists:
+ // This can happen if two clients try to create the same lobby at the same time, a classic race condition.
+ // Try to join the other client's newly created chat lobby instead.
+ connectedLobbyId = null;
+ Debug.Log("Chat lobby already exists, attempting to join it...");
+ ConnectToChat((string)data.ClientData, onCompleted);
+ break;
+ default:
+ connectedLobbyId = null;
+ Debug.LogError($"Chat lobby creation failed, result code = {data.ResultCode}");
+ onCompleted?.Invoke(false);
+ break;
+ }
+ }
+
+ private void HandleLobbyJoined(JoinLobbyCallbackInfo data, Action onCompleted)
+ {
+ switch (data.ResultCode)
+ {
+ case Result.Success:
+ connectedLobbyId = data.LobbyId;
+ Debug.Log($"Chat lobby joined successfully, lobby ID = {connectedLobbyId}");
+ onCompleted?.Invoke(true);
+ break;
+ default:
+ connectedLobbyId = null;
+ Debug.LogError($"Chat lobby join failed, result code = {data.ResultCode}");
+ onCompleted?.Invoke(false);
+ break;
+ }
+ }
+
+ public void DisconnectChat()
+ {
+ if (!IsConnected)
+ return;
+
+ lobbyInterface.LeaveLobby
+ (
+ new LeaveLobbyOptions
+ {
+ LocalUserId = productUserProvider.Invoke(),
+ LobbyId = connectedLobbyId,
+ },
+ null,
+ data => { }
+ );
+
+ connectedLobbyId = null;
+ }
+
+ ///
+ /// Mute or unmute the local player's voice chat. This can be used to implement push-to-talk.
+ ///
+ public void SetLocalMuted(bool muted)
+ {
+ if (!IsConnected)
+ return;
+
+
+ }
+
+ ///
+ /// Mute or unmute a specific remove player. This can be used to filter out specific players in the chat lobby,
+ /// or for manually muting toxic players.
+ ///
+ public void SetRemoteMuted(string playerId, bool muted)
+ {
+ if (!IsConnected)
+ return;
+ }
+
+ ///
+ /// Set all remote players to muted. This can be used during loading screens, or to initialize the chat lobby
+ /// when chatting with only a small subset of players.
+ ///
+ public void MuteAllRemote()
+ {
+ if (!IsConnected)
+ return;
+ }
+}
diff --git a/Assets/Scripts/EOSVoiceChat.cs.meta b/Assets/Scripts/EOSVoiceChat.cs.meta
new file mode 100644
index 0000000..389805c
--- /dev/null
+++ b/Assets/Scripts/EOSVoiceChat.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f668ddd1628206e4b83baeb572784acd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/MagnificentVoiceChat.cs b/Assets/Scripts/MagnificentVoiceChat.cs
index 89b5a58..85c165c 100644
--- a/Assets/Scripts/MagnificentVoiceChat.cs
+++ b/Assets/Scripts/MagnificentVoiceChat.cs
@@ -33,7 +33,7 @@ public class MagnificentVoiceChat : MonoBehaviour
private EpicAccountId localEpicAccountId;
private ProductUserId localProductUserId;
- private string lobbyId;
+ private EOSVoiceChat voiceChat;
private string XAudio29DllPath =>
#if UNITY_EDITOR
@@ -125,6 +125,8 @@ public class MagnificentVoiceChat : MonoBehaviour
Credentials = GetEpicCredentials(),
ScopeFlags = AuthScopeFlags.BasicProfile,
}, null, HandleLoginResult);
+
+ voiceChat = new EOSVoiceChat(lobbyInterface, rtcInterface, audioInterface, () => localProductUserId);
}
private Credentials GetEpicCredentials() // This is platform-specific
@@ -213,80 +215,16 @@ public class MagnificentVoiceChat : MonoBehaviour
private void CreateOrJoinVoiceLobby()
{
- lobbyInterface.CreateLobbySearch(new CreateLobbySearchOptions { MaxResults = 1 }, out var searchHandle);
- searchHandle.SetLobbyId(new LobbySearchSetLobbyIdOptions { LobbyId = DebugLobbyId });
- searchHandle.Find(new LobbySearchFindOptions { LocalUserId = localProductUserId }, null, findData =>
+ voiceChat.ConnectToChat(DebugLobbyId, success =>
{
- switch (findData.ResultCode)
- {
- case Result.Success:
- searchHandle.CopySearchResultByIndex(new LobbySearchCopySearchResultByIndexOptions { LobbyIndex = 0 }, out var lobbyDetails);
-
- Debug.Log("Found existing lobby, joining...");
- status.AppendLine("Found existing lobby, joining...");
-
- lobbyInterface.JoinLobby(new JoinLobbyOptions
- {
- LocalUserId = localProductUserId,
- LobbyDetailsHandle = lobbyDetails,
- PresenceEnabled = false,
- }, null, HandleLobbyJoined);
-
- break;
- default:
- Debug.Log("Creating new lobby...");
- status.AppendLine("Creating new lobby...");
-
- // Lobby creation options should probably be platform-specific; generated lobby ID and max members depends on the chat scheme
- lobbyInterface.CreateLobby(new CreateLobbyOptions
- {
- LocalUserId = localProductUserId,
- AllowInvites = false,
- PermissionLevel = LobbyPermissionLevel.Publicadvertised,
- PresenceEnabled = false,
- MaxLobbyMembers = 16, // "Lobbies that generate conference rooms must have <= 16 max players"
- DisableHostMigration = false,
- LobbyId = DebugLobbyId,
- BucketId = DebugBucketId, // Something that combines front, game mode, region, etc (or maybe just the room ID)
- EnableRTCRoom = true,
- }, null, HandleLobbyCreated);
- break;
- }
+ Debug.Log($"Chat lobby connect result = {success}");
+ if (success)
+ status.AppendLine("Chat lobby successfully connected!");
+ else
+ status.AppendLine("Chat lobby connect failure...");
});
}
- private void HandleLobbyCreated(CreateLobbyCallbackInfo data)
- {
- switch (data.ResultCode)
- {
- case Result.Success:
- lobbyId = data.LobbyId;
- Debug.Log("Lobby creation successful: " + lobbyId);
- status.AppendLine("Lobby creation successful: " + lobbyId);
- break;
- default:
- Debug.Log("Lobby creation failed, result code = " + data.ResultCode);
- status.AppendLine("Lobby creation failed");
- break;
- }
- }
-
- private void HandleLobbyJoined(JoinLobbyCallbackInfo data)
- {
- switch (data.ResultCode)
- {
- case Result.Success:
- lobbyId = data.LobbyId;
- Debug.Log("Lobby joined successfully: " + lobbyId);
- status.AppendLine("Lobby joined successfully: " + lobbyId);
- break;
- default:
- Debug.Log("Lobby join failed, result code = " + data.ResultCode);
- status.AppendLine("Lobby join failed");
- break;
- }
- }
-
void Update()
{
platformInterface.Tick();
@@ -294,15 +232,10 @@ public class MagnificentVoiceChat : MonoBehaviour
private void OnDestroy()
{
- if (lobbyInterface != null && lobbyId != null)
+ if (voiceChat != null)
{
- lobbyInterface.LeaveLobby(new LeaveLobbyOptions
- {
- LocalUserId = localProductUserId,
- LobbyId = lobbyId,
- }, null, data => { });
-
- lobbyId = null;
+ voiceChat.DisconnectChat();
+ voiceChat = null;
}
if (platformInterface != null)