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
var connectArgs = new ChatConnectArgs(chatLobbyName, onCompleted, maxChatPlayers);
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,
},
connectArgs,
HandleLobbyJoined
);
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,
},
connectArgs,
HandleLobbyCreated
);
break;
}
});
}
private void HandleLobbyCreated(CreateLobbyCallbackInfo data)
{
var connectArgs = (ChatConnectArgs)data.ClientData;
switch (data.ResultCode)
{
case Result.Success:
connectedLobbyId = data.LobbyId;
Debug.Log($"Chat lobby creation successful, lobby ID = {connectedLobbyId}");
connectArgs.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(connectArgs.chatLobbyName, connectArgs.onCompleted, connectArgs.maxChatPlayers);
break;
default:
connectedLobbyId = null;
Debug.LogError($"Chat lobby creation failed, result code = {data.ResultCode}");
connectArgs.onCompleted?.Invoke(false);
break;
}
}
private void HandleLobbyJoined(JoinLobbyCallbackInfo data)
{
var connectArgs = (ChatConnectArgs)data.ClientData;
switch (data.ResultCode)
{
case Result.Success:
connectedLobbyId = data.LobbyId;
Debug.Log($"Chat lobby joined successfully, lobby ID = {connectedLobbyId}");
connectArgs.onCompleted?.Invoke(true);
break;
default:
connectedLobbyId = null;
Debug.LogError($"Chat lobby join failed, result code = {data.ResultCode}");
connectArgs.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;
}
private class ChatConnectArgs
{
public readonly string chatLobbyName;
public readonly Action onCompleted;
public readonly uint maxChatPlayers;
public ChatConnectArgs(string chatLobbyName, Action onCompleted, uint maxChatPlayers)
{
this.chatLobbyName = chatLobbyName;
this.onCompleted = onCompleted;
this.maxChatPlayers = maxChatPlayers;
}
}
}