// Notes: // In Mirror 89.11.0, the OnServerConnectedWithAddress event was added // https://github.com/MirrorNetworking/Mirror/releases/tag/v89.11.0 // OnServerConnected no longer seems to work? #if UNIVOICE_MIRROR_NETWORK || UNIVOICE_NETWORK_MIRROR using System; using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; using UnityEngine; using Mirror; using Adrenak.BRW; namespace Adrenak.UniVoice.Networks { /// /// Activate this class by including the UNIVOICE_MIRROR_NETWORK compilaton symbol /// in your project. /// This is an implementation of the interface for Mirror. /// It uses the Mirror transport to send and receive UniVoice audio data to and from clients. /// public class MirrorServer : IAudioServer { const string TAG = "[MirrorServer]"; public event Action OnServerStart; public event Action OnServerStop; public event Action OnClientVoiceSettingsUpdated; public List ClientIDs { get; private set; } public Dictionary ClientVoiceSettings { get; private set; } readonly MirrorModeObserver mirrorEvents; public MirrorServer() { ClientIDs = new List(); ClientVoiceSettings = new Dictionary(); mirrorEvents = MirrorModeObserver.New(); mirrorEvents.ModeChanged += OnModeChanged; NetworkServer.RegisterHandler(OnReceivedMessage, false); #if MIRROR_89_OR_NEWER NetworkManager.singleton.transport.OnServerConnectedWithAddress += OnServerConnected; #else NetworkManager.singleton.transport.OnServerConnected += OnServerConnected; #endif NetworkManager.singleton.transport.OnServerDisconnected += OnServerDisconnected; } void IDisposable.Dispose() { mirrorEvents.ModeChanged -= OnModeChanged; NetworkServer.UnregisterHandler(); #if MIRROR_89_OR_NEWER NetworkManager.singleton.transport.OnServerConnectedWithAddress -= OnServerConnected; #else NetworkManager.singleton.transport.OnServerConnected -= OnServerConnected; #endif NetworkManager.singleton.transport.OnServerDisconnected -= OnServerDisconnected; } void OnModeChanged(NetworkManagerMode oldMode, NetworkManagerMode newMode) { NetworkServer.ReplaceHandler(OnReceivedMessage, false); if((newMode == NetworkManagerMode.ServerOnly || newMode == NetworkManagerMode.Host) && (oldMode != NetworkManagerMode.ServerOnly && oldMode != NetworkManagerMode.Host)) { OnServerStart?.Invoke(); } else if(newMode == NetworkManagerMode.Offline) { ClientIDs.Clear(); ClientVoiceSettings.Clear(); OnServerStop?.Invoke(); } } void OnReceivedMessage(NetworkConnectionToClient connection, MirrorMessage message) { var clientId = connection.connectionId; var reader = new BytesReader(message.data); var tag = reader.ReadString(); if (tag.Equals(MirrorMessageTags.AUDIO_FRAME)) { // We start with all the peers except the one that's // sent the audio var peersToForwardAudioTo = ClientIDs .Where(x => x != clientId); // Consider the voice settings of the sender to see who // the sender doesn't want to send audio to if (ClientVoiceSettings.TryGetValue(clientId, out var senderSettings)) { // If the client sending the audio has deafened everyone // to their audio, we simply return if (senderSettings.deafenAll) return; // Else, we remove all the peers that the sender has // deafened themselves to peersToForwardAudioTo = peersToForwardAudioTo .Where(x => !senderSettings.deafenedPeers.Contains(x)); } // We iterate through each recipient peer that the sender wants to send // audio to, checking if they have muted the sender in which case // we skip that recipient foreach (var receiver in peersToForwardAudioTo) { if (ClientVoiceSettings.TryGetValue(receiver, out var receiverSettings)) { if (receiverSettings.muteAll) continue; if (receiverSettings.mutedPeers.Contains(clientId)) continue; } SendToClient(receiver, message.data, Channels.Unreliable); } } else if (tag.Equals(MirrorMessageTags.VOICE_SETTINGS)) { //Debug.unityLogger.Log(LogType.Log, TAG, "Mirror server stopped"); // We create the VoiceSettings object by reading from the reader // and update the peer voice settings map var muteAll = reader.ReadInt() == 1 ? true : false; var mutedPeers = reader.ReadIntArray().ToList(); var deafenAll = reader.ReadInt() == 1 ? true : false; var deafenedPeers = reader.ReadIntArray().ToList(); var voiceSettings = new VoiceSettings { muteAll = muteAll, mutedPeers = mutedPeers, deafenAll = deafenAll, deafenedPeers = deafenedPeers }; if (ClientVoiceSettings.ContainsKey(clientId)) ClientVoiceSettings[clientId] = voiceSettings; else ClientVoiceSettings.Add(clientId, voiceSettings); OnClientVoiceSettingsUpdated?.Invoke(); } } // When a new Mirror client connects #if MIRROR_89_OR_NEWER void OnServerConnected(int connId, string addr) { #else void OnServerConnected(int connId) { #endif NetworkServer.ReplaceHandler(OnReceivedMessage, false); Debug.unityLogger.Log(LogType.Log, TAG, $"Client {connId} connected"); ClientIDs.Add(connId); foreach (var peer in ClientIDs) { // To the new peer, we send data to initialize it with. // This includes the following: // - its own ID (int) This tells the new peer its ID in the chatroom // - IDs of other peers (int[]) This tells the new peer the IDs of the // peers that are already in the chatroom if (peer == connId) { // Get all the existing peer IDs except that of the newly joined peer var otherPeerIDs = ClientIDs .Where(x => x != connId) .ToArray(); var newClientPacket = new BytesWriter() .WriteString(MirrorMessageTags.PEER_INIT) .WriteInt(connId) .WriteIntArray(otherPeerIDs); // We initialize the new client/peer with some delay. A delay here MAY not be // required but I faced some issues with immediate initialization earlier. SendToClientDelayed(connId, newClientPacket.Bytes, Channels.Reliable, 100); string peerListString = string.Join(", ", otherPeerIDs); Debug.unityLogger.Log(LogType.Log, TAG, $"Initializing new client with ID {connId} and peer list {peerListString}"); } // To the already existing peers, we let them know a new peer has joined // by sending the new peer ID to them. else { var newPeerNotifyPacked = new BytesWriter() .WriteString(MirrorMessageTags.PEER_JOINED) .WriteInt(connId); Debug.unityLogger.Log( LogType.Log, TAG, $"Notified client {peer} about new client {connId}"); SendToClient(peer, newPeerNotifyPacked.Bytes, Channels.Reliable); } } } void OnServerDisconnected(int connId) { NetworkServer.ReplaceHandler(OnReceivedMessage, false); ClientIDs.Remove(connId); Debug.unityLogger.Log(LogType.Log, TAG, $"Client {connId} disconnected"); // Notify all remaining peers that a peer has left foreach (var peerId in ClientIDs) { var packet = new BytesWriter() .WriteString(MirrorMessageTags.PEER_LEFT) .WriteInt(connId); Debug.unityLogger.Log(LogType.Log, TAG, $"Notified client {peerId} about {connId} leaving"); SendToClient(peerId, packet.Bytes, Channels.Reliable); } } async void SendToClientDelayed(int peerID, byte[] bytes, int channel, int delayMS) { await Task.Delay(delayMS); SendToClient(peerID, bytes, channel); } void SendToClient(int clientConnId, byte[] bytes, int channel) { var message = new MirrorMessage { data = bytes }; var conn = GetConnectionToClient(clientConnId); if (conn != null) conn.Send(message, channel); } NetworkConnectionToClient GetConnectionToClient(int connId) { foreach(var conn in NetworkServer.connections) if (conn.Key == connId) return conn.Value; return null; } } } #endif