#if FISHNET
using System;
using System.Collections.Generic;
using System.Linq;
using Adrenak.BRW;
using FishNet;
using FishNet.Managing;
using FishNet.Transporting;
using UnityEngine;
namespace Adrenak.UniVoice.Networks
{
///
/// This is the implementation of interface for FishNet.
/// It uses the FishNet to send and receive UniVoice data to the server.
///
public class FishNetClient : IAudioClient
{
private const string TAG = "[FishNetClient]";
public int ID { get; private set; } = -1;
public List PeerIDs { get; private set; }
public VoiceSettings YourVoiceSettings { get; private set; }
public event Action> OnJoined;
public event Action OnLeft;
public event Action OnPeerJoined;
public event Action OnPeerLeft;
public event Action OnReceivedPeerAudioFrame;
private NetworkManager _networkManager;
public FishNetClient()
{
PeerIDs = new List();
YourVoiceSettings = new VoiceSettings();
_networkManager = InstanceFinder.NetworkManager;
_networkManager.ClientManager.OnClientConnectionState += OnClientConnectionStateChanged;
_networkManager.ClientManager.OnAuthenticated += OnClientAuthenticated;
_networkManager.ClientManager.OnRemoteConnectionState += OnRemoteConnectionStateChanged;
_networkManager.ClientManager.RegisterBroadcast(OnReceivedMessage);
}
public void Dispose()
{
if (_networkManager != null)
{
_networkManager.ClientManager.OnClientConnectionState -= OnClientConnectionStateChanged;
_networkManager.ClientManager.OnAuthenticated -= OnClientAuthenticated;
_networkManager.ClientManager.OnRemoteConnectionState -= OnRemoteConnectionStateChanged;
_networkManager.ClientManager.UnregisterBroadcast(OnReceivedMessage);
}
PeerIDs.Clear();
}
private void OnRemoteConnectionStateChanged(RemoteConnectionStateArgs args)
{
// Don't process connection state changes before the client is authenticated
if (_networkManager.ClientManager.Connection.ClientId < 0)
return;
if (args.ConnectionState == RemoteConnectionState.Started)
{
var newPeerID = args.ConnectionId;
if (!PeerIDs.Contains(newPeerID))
{
PeerIDs.Add(newPeerID);
Debug.unityLogger.Log(LogType.Log, TAG,
$"Peer {newPeerID} joined. Peer list is now {string.Join(", ", PeerIDs)}");
OnPeerJoined?.Invoke(newPeerID);
}
}
else if (args.ConnectionState == RemoteConnectionState.Stopped)
{
var leftPeerID = args.ConnectionId;
if (PeerIDs.Contains(leftPeerID))
{
PeerIDs.Remove(leftPeerID);
var log2 = $"Peer {leftPeerID} left. ";
if (PeerIDs.Count == 0)
log2 += "There are no peers anymore.";
else
log2 += $"Peer list is now {string.Join(", ", PeerIDs)}";
Debug.unityLogger.Log(LogType.Log, TAG, log2);
OnPeerLeft?.Invoke(leftPeerID);
}
}
}
private void OnClientAuthenticated()
{
// We need to use OnClientAuthenticated to ensure the client does have ClientId set
ID = _networkManager.ClientManager.Connection.ClientId;
PeerIDs = _networkManager.ClientManager.Clients.Keys.Where(x => x != ID).ToList();
var log = $"Initialized with ID {ID}. ";
if (PeerIDs.Count > 0)
log += $"Peer list: {string.Join(", ", PeerIDs)}";
else
log += "There are currently no peers.";
Debug.unityLogger.Log(LogType.Log, TAG, log);
OnJoined?.Invoke(ID, PeerIDs);
foreach (var peerId in PeerIDs)
OnPeerJoined?.Invoke(peerId);
}
private void OnClientConnectionStateChanged(ClientConnectionStateArgs args)
{
// We check only for the stopped state here, as the started state is handled in OnClientAuthenticated
if (args.ConnectionState == LocalConnectionState.Stopped)
{
YourVoiceSettings = new VoiceSettings();
var oldPeerIds = PeerIDs.ToList();
PeerIDs.Clear();
ID = -1;
foreach (var peerId in oldPeerIds)
OnPeerLeft?.Invoke(peerId);
OnLeft?.Invoke();
}
}
private void OnReceivedMessage(FishNetBroadcast msg, Channel channel)
{
var reader = new BytesReader(msg.data);
var tag = reader.ReadString();
switch (tag)
{
// When the server sends audio from a peer meant for this client
case FishNetBroadcastTags.AUDIO_FRAME:
var sender = reader.ReadInt();
if (sender == ID || !PeerIDs.Contains(sender))
return;
var frame = new AudioFrame
{
timestamp = reader.ReadLong(),
frequency = reader.ReadInt(),
channelCount = reader.ReadInt(),
samples = reader.ReadByteArray()
};
OnReceivedPeerAudioFrame?.Invoke(sender, frame);
break;
}
}
///
/// Sends an audio frame captured on this client to the server
///
///
public void SendAudioFrame(AudioFrame frame)
{
if (ID == -1)
return;
var writer = new BytesWriter();
writer.WriteString(FishNetBroadcastTags.AUDIO_FRAME);
writer.WriteInt(ID);
writer.WriteLong(frame.timestamp);
writer.WriteInt(frame.frequency);
writer.WriteInt(frame.channelCount);
writer.WriteByteArray(frame.samples);
var message = new FishNetBroadcast
{
data = writer.Bytes
};
if (_networkManager.ClientManager.Started)
_networkManager.ClientManager.Broadcast(message, Channel.Unreliable);
}
///
/// Updates the server with the voice settings of this client
///
public void SubmitVoiceSettings()
{
if (ID == -1)
return;
Debug.unityLogger.Log(TAG, "Submitting : " + YourVoiceSettings);
var writer = new BytesWriter();
writer.WriteString(FishNetBroadcastTags.VOICE_SETTINGS);
writer.WriteInt(YourVoiceSettings.muteAll ? 1 : 0);
writer.WriteIntArray(YourVoiceSettings.mutedPeers.ToArray());
writer.WriteInt(YourVoiceSettings.deafenAll ? 1 : 0);
writer.WriteIntArray(YourVoiceSettings.deafenedPeers.ToArray());
writer.WriteStringArray(YourVoiceSettings.myTags.ToArray());
writer.WriteStringArray(YourVoiceSettings.mutedTags.ToArray());
writer.WriteStringArray(YourVoiceSettings.deafenedTags.ToArray());
var message = new FishNetBroadcast() {
data = writer.Bytes
};
_networkManager.ClientManager.Broadcast(message);
}
public void UpdateVoiceSettings(Action modification) {
modification?.Invoke(YourVoiceSettings);
SubmitVoiceSettings();
}
}
}
#endif