/* * ----------------------- * PlaySound() * ----------------------- */ static public int PlaySound(AudioClip clip, float volume, EmitterChannel src = EmitterChannel.Any, float delay = 0.0f, float pitchVariance = 1.0f, bool loop = false) { if (!SoundEnabled) { return(-1); } return(PlaySoundAt((staticListenerPosition != null) ? staticListenerPosition.position : Vector3.zero, clip, volume, src, delay, pitchVariance, loop)); }
/* * ----------------------- * PlaySound() * ----------------------- */ static public int PlaySound(SoundFX soundFX, EmitterChannel src = EmitterChannel.Any, float delay = 0.0f) { if (!SoundEnabled) { return(-1); } return(PlaySoundAt((staticListenerPosition != null) ? staticListenerPosition.position : Vector3.zero, soundFX, src, delay)); }
/// <summary> /// Constructs a new instance of a profiler, bound to a particular thread. /// </summary> /// <param name="channel">The channel for emitter.io.</param> /// <param name="interval">The interval to publish measurements info.</param> public Instrument(string channel, TimeSpan interval) : base(interval) { this.Channel = channel; // Get the channel if (!EmitterChannel.TryParse(this.Channel, false, out Info)) { throw new ArgumentException("Invalid channel"); } }
/// <summary> /// Attempts to generate the key and returns the result. /// </summary> /// <param name="client">The remote client.</param> /// <param name="channel">The full channel string.</param> /// <param name="message">The message to publish.</param> public static EmitterResponse Process(IClient client, EmitterChannel channel, KeyGenRequest request) { // Parse the channel EmitterChannel info; if (!EmitterChannel.TryParse(request.Channel, false, out info)) { return(EmitterError.BadRequest); } // Should be a static (non-wildcard) channel string. if (info.Type != ChannelType.Static) { return(EmitterError.BadRequest); } // Attempt to parse the key, this should be a master key SecurityKey masterKey; if (!SecurityKey.TryParse(request.Key, out masterKey) || !masterKey.IsMaster || masterKey.IsExpired) { return(EmitterError.Unauthorized); } // Attempt to fetch the contract using the key. Underneath, it's cached. var contract = Services.Contract.GetByKey(masterKey.Contract) as EmitterContract; if (contract == null) { return(EmitterError.NotFound); } // Validate the contract if (!contract.Validate(ref masterKey)) { return(EmitterError.Unauthorized); } // Generate the key var key = SecurityKey.Create(); key.Master = masterKey.Master; key.Contract = contract.Oid; key.Signature = contract.Signature; key.Permissions = request.Access; key.Target = SecurityHash.GetHash(request.Channel); key.Expires = request.Expires; return(new KeyGenResponse() { Key = key.Value, Channel = request.Channel }); }
/// <summary> /// Attempts to get an option. /// </summary> public static bool TryGet(EmitterChannel channel, string option, int defaultValue, out int value) { // Do we have the option? value = defaultValue; if (channel.Options == null || !channel.Options.ContainsKey(option)) { return(false); } // Do we have a TTL with the message? return(int.TryParse(channel.Options[option], out value)); }
/// <summary> /// Attempts to generate the key and returns the result. /// </summary> /// <param name="client">The remote client.</param> /// <param name="info">The full channel string.</param> /// <param name="message">The message to publish.</param> public static bool TryProcess(IClient client, EmitterChannel info, ArraySegment <byte> message) { // Is this a special api request? if (info.Key != EmitterConst.ApiPrefix) { return(false); } // The response to send out, default to bad request EmitterResponse response = EmitterError.BadRequest; try { switch (info.Target) { case 548658350: // Handle the 'keygen' request. response = HandleKeyGen.Process(client, info, message.As <KeyGenRequest>()); return(true); case 3869262148: // Handle the 'presence' request. response = HandlePresence.Process(client, info, message.As <PresenceRequest>()); return(true); default: // We've got a bad request response = EmitterError.BadRequest; return(true); } } catch (NotImplementedException) { // We've got a not implemented exception response = EmitterError.NotImplemented; return(true); } catch (Exception ex) { // We've got a an internal error Service.Logger.Log(ex); response = EmitterError.ServerError; return(true); } finally { // Send the response, always if (response != null) { SendResponse(client, "emitter/" + info.Channel, response); } } }
/* ----------------------- FindFreeEmitter() ----------------------- */ static private int FindFreeEmitter( EmitterChannel src, SoundPriority priority ) { // default to the reserved emitter SoundEmitter next = theAudioManager.soundEmitters[0]: if ( src == EmitterChannel.Any ) { // pull from the free emitter list if possible if ( theAudioManager.nextFreeEmitters.size > 0 ) { // return the first in the list next = theAudioManager.nextFreeEmitters[0]: // remove it from the free list theAudioManager.nextFreeEmitters.RemoveAtFast( 0 ): } else { // no free emitters available so pull from the lowest priority sound if ( priority == SoundPriority.VeryLow ) { // skip low priority sounds return -1: } else { // find a playing emitter that has a lower priority than what we're requesting // TODO - we could first search for Very Low, then Low, etc ... TBD if it's worth the effort next = theAudioManager.playingEmitters.Find( item => item != null && item.priority < priority ): if ( next == null ) { // last chance to find a free emitter if ( priority < SoundPriority.Default ) { // skip sounds less than the default priority if ( theAudioManager.verboseLogging ) { Debug.LogWarning( "[AudioManager] skipping sound " + priority ): } return -1: } else { // grab a default priority emitter so that we don't cannabalize a high priority sound next = theAudioManager.playingEmitters.Find( item => item != null && item.priority <= SoundPriority.Default ): : } } if ( next != null ) { if ( theAudioManager.verboseLogging ) { Debug.LogWarning( "[AudioManager] cannabalizing " + next.originalIdx + " Time: " + Time.time ): } // remove it from the playing list next.Stop(): theAudioManager.playingEmitters.RemoveFast( next ): } } } } if ( next == null ) { Debug.LogError( "[AudioManager] ERROR - absolutely couldn't find a free emitter! Priority = " + priority + " TOO MANY PlaySound* calls!" ): showPlayingEmitterCount = true: return -1: } return next.originalIdx: }
/* * ----------------------- * PlaySoundAt() * ----------------------- */ static public int PlaySoundAt(Vector3 position, AudioClip clip, float volume = 1.0f, EmitterChannel src = EmitterChannel.Any, float delay = 0.0f, float pitch = 1.0f, bool loop = false) { if (!SoundEnabled) { return(-1); } if (clip == null) { return(-1); } // check the distance from the local player and ignore sounds out of range if (staticListenerPosition != null) { if ((staticListenerPosition.position - position).sqrMagnitude > theAudioManager.audioMaxFallOffDistanceSqr) { // no chance of being heard return(-1); } } int idx = FindFreeEmitter(src, 0); if (idx == -1) { // no free emitters - should only happen on very low priority sounds return(-1); } SoundEmitter emitter = theAudioManager.soundEmitters[idx]; // make sure to detach the emitter from a previous parent emitter.ResetParent(soundEmitterParent.transform); emitter.gameObject.SetActive(true); // set up the sound emitter AudioSource audioSource = emitter.audioSource; ONSPAudioSource osp = emitter.osp; audioSource.enabled = true; audioSource.volume = Mathf.Clamp01(theAudioManager.volumeSoundFX * volume); audioSource.pitch = pitch; audioSource.spatialBlend = 0.8f; audioSource.rolloffMode = AudioRolloffMode.Linear; audioSource.SetCustomCurve(AudioSourceCurveType.ReverbZoneMix, defaultReverbZoneMix); audioSource.dopplerLevel = 0.0f; audioSource.clip = clip; audioSource.spread = 0.0f; audioSource.loop = loop; audioSource.mute = false; audioSource.minDistance = theAudioManager.audioMinFallOffDistance; audioSource.maxDistance = theAudioManager.audioMaxFallOffDistance; audioSource.outputAudioMixerGroup = AudioManager.EmitterGroup; // set the play time so we can check when sounds are done emitter.endPlayTime = Time.time + clip.length + delay; // cache the default volume for fading emitter.defaultVolume = audioSource.volume; // default priority emitter.priority = 0; // reset this emitter.onFinished = null; // update the sound group limits emitter.SetPlayingSoundGroup(null); // add to the playing list if (src == EmitterChannel.Any) { theAudioManager.playingEmitters.AddUnique(emitter); } // disable spatialization (by default for regular AudioClips) if (osp != null) { osp.EnableSpatialization = false; } audioSource.transform.position = position; if (theAudioManager.verboseLogging) { Debug.Log("[AudioManager] PlaySoundAt() channel = " + idx + " clip = " + clip.name + " volume = " + emitter.volume + " Delay = " + delay + " time = " + Time.time + "\n"); } // play the sound if (delay > 0f) { audioSource.PlayDelayed(delay); } else { audioSource.Play(); } return(idx); }
/* * ----------------------- * PlayRandomSoundAt() * ----------------------- */ static public int PlayRandomSoundAt(Vector3 position, AudioClip[] clips, float volume, EmitterChannel src = EmitterChannel.Any, float delay = 0.0f, float pitch = 1.0f, bool loop = false) { if ((clips == null) || (clips.Length == 0)) { return(-1); } int idx = Random.Range(0, clips.Length); return(PlaySoundAt(position, clips[idx], volume, src, delay, pitch, loop)); }
/* * ----------------------- * PlaySoundAt() * ----------------------- */ static public int PlaySoundAt(Vector3 position, SoundFX soundFX, EmitterChannel src = EmitterChannel.Any, float delay = 0.0f, float volumeOverride = 1.0f, float pitchMultiplier = 1.0f) { if (!SoundEnabled) { return(-1); } AudioClip clip = soundFX.GetClip(); if (clip == null) { return(-1); } // check the distance from the local player and ignore sounds out of range if (staticListenerPosition != null) { float distFromListener = (staticListenerPosition.position - position).sqrMagnitude; if (distFromListener > theAudioManager.audioMaxFallOffDistanceSqr) { return(-1); } if (distFromListener > soundFX.MaxFalloffDistSquared) { return(-1); } } // check max playing sounds if (soundFX.ReachedGroupPlayLimit()) { if (theAudioManager.verboseLogging) { Debug.Log("[AudioManager] PlaySoundAt() with " + soundFX.name + " skipped due to group play limit"); } return(-1); } int idx = FindFreeEmitter(src, soundFX.priority); if (idx == -1) { // no free emitters - should only happen on very low priority sounds return(-1); } SoundEmitter emitter = theAudioManager.soundEmitters[idx]; // make sure to detach the emitter from a previous parent emitter.ResetParent(soundEmitterParent.transform); emitter.gameObject.SetActive(true); // set up the sound emitter AudioSource audioSource = emitter.audioSource; ONSPAudioSource osp = emitter.osp; audioSource.enabled = true; audioSource.volume = Mathf.Clamp01(Mathf.Clamp01(theAudioManager.volumeSoundFX * soundFX.volume) * volumeOverride * soundFX.GroupVolumeOverride); audioSource.pitch = soundFX.GetPitch() * pitchMultiplier; audioSource.time = 0.0f; audioSource.spatialBlend = 1.0f; audioSource.rolloffMode = soundFX.falloffCurve; if (soundFX.falloffCurve == AudioRolloffMode.Custom) { audioSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, soundFX.volumeFalloffCurve); } audioSource.SetCustomCurve(AudioSourceCurveType.ReverbZoneMix, soundFX.reverbZoneMix); audioSource.dopplerLevel = 0; audioSource.clip = clip; audioSource.spread = soundFX.spread; audioSource.loop = soundFX.looping; audioSource.mute = false; audioSource.minDistance = soundFX.falloffDistance.x; audioSource.maxDistance = soundFX.falloffDistance.y; audioSource.outputAudioMixerGroup = soundFX.GetMixerGroup(AudioManager.EmitterGroup); // set the play time so we can check when sounds are done emitter.endPlayTime = Time.time + clip.length + delay; // cache the default volume for fading emitter.defaultVolume = audioSource.volume; // sound priority emitter.priority = soundFX.priority; // reset this emitter.onFinished = null; // update the sound group limits emitter.SetPlayingSoundGroup(soundFX.Group); // add to the playing list if (src == EmitterChannel.Any) { theAudioManager.playingEmitters.AddUnique(emitter); } // OSP properties if (osp != null) { osp.EnableSpatialization = soundFX.ospProps.enableSpatialization; osp.EnableRfl = theAudioManager.enableSpatializedFastOverride || soundFX.ospProps.useFastOverride ? true : false; osp.Gain = soundFX.ospProps.gain; osp.UseInvSqr = soundFX.ospProps.enableInvSquare; osp.Near = soundFX.ospProps.invSquareFalloff.x; osp.Far = soundFX.ospProps.invSquareFalloff.y; audioSource.spatialBlend = (soundFX.ospProps.enableSpatialization) ? 1.0f : 0.8f; // make sure to set the properties in the audio source before playing osp.SetParameters(ref audioSource); } audioSource.transform.position = position; if (theAudioManager.verboseLogging) { Debug.Log("[AudioManager] PlaySoundAt() channel = " + idx + " soundFX = " + soundFX.name + " volume = " + emitter.volume + " Delay = " + delay + " time = " + Time.time + "\n"); } // play the sound if (delay > 0f) { audioSource.PlayDelayed(delay); } else { audioSource.Play(); } return(idx); }
/// <summary> /// Attempts to publish and returns the result. /// </summary> /// <param name="client">The remote client.</param> /// <param name="channel">The full channel string.</param> /// <param name="message">The message to publish.</param> public static EmitterEventCode Process(IClient client, string channel, ArraySegment <byte> message) { try { // Parse the channel EmitterChannel info; if (!EmitterChannel.TryParse(channel, true, out info)) { return(EmitterEventCode.BadRequest); } // Is this a special api request? if (HandleRequest.TryProcess(client, info, message)) { return(EmitterEventCode.Success); } // Publish should only have static channel strings if (info.Type != ChannelType.Static) { return(EmitterEventCode.Forbidden); } // Attempt to parse the key SecurityKey key; if (!SecurityKey.TryParse(info.Key, out key)) { return(EmitterEventCode.BadRequest); } // Has the key expired? if (key.IsExpired) { return(EmitterEventCode.Unauthorized); } // Attempt to fetch the contract using the key. Underneath, it's cached. var contract = Services.Contract.GetByKey(key.Contract) as EmitterContract; if (contract == null) { return(EmitterEventCode.NotFound); } // Check if the payment state is valid if (contract.Status == EmitterContractStatus.Refused) { return(EmitterEventCode.PaymentRequired); } // Validate the contract if (!contract.Validate(ref key)) { return(EmitterEventCode.Unauthorized); } // Check if the key has the permission to write here if (!key.HasPermission(SecurityAccess.Write)) { return(EmitterEventCode.Unauthorized); } // Check if the key has the permission for the required channel if (key.Target != 0 && info.Target != key.Target) { return(EmitterEventCode.Unauthorized); } // Do we have a TTL with the message? int ttl; info.HasTimeToLive(out ttl); // Check if the key has a TTL and also can store (soft permission) if (ttl > 0 && !key.HasPermission(SecurityAccess.Store)) { ttl = 0; } // Publish within the service Dispatcher.Publish(contract, info.Target, info.Channel, message, ttl); // Successfully published return(EmitterEventCode.Success); } catch (NotImplementedException) { // We've got a not implemented exception return(EmitterEventCode.NotImplemented); } catch (Exception ex) { // We need to log it Service.Logger.Log(ex); // We've got a an internal error return(EmitterEventCode.ServerError); } }
/* * ----------------------- * SetChannel() * ----------------------- */ public void SetChannel(int _channel) { channel = (EmitterChannel)_channel; }
/// <summary> /// Occurs when the remote client attempts to subscribe to a hub. /// </summary> /// <param name="client">The remote client.</param> /// <param name="channel">The full channel string.</param> public static EmitterEventCode Process(IClient client, string channel) { try { // Parse the channel EmitterChannel info; if (!EmitterChannel.TryParse(channel, true, out info)) { return(EmitterEventCode.BadRequest); } // Simple ACK for api subscribe. We don't really need to subscribe as // this uses request/response topology and hence the response is sent // through the same TCP connection. if (info.Key == "emitter") { return(EmitterEventCode.Success); } // Attempt to parse the key SecurityKey key; if (!SecurityKey.TryParse(info.Key, out key)) { return(EmitterEventCode.BadRequest); } // Has the key expired? if (key.IsExpired) { return(EmitterEventCode.Unauthorized); } // Have we already subscribed? //if (client[channel] != null) // return EmitterEventCode.Success; // Attempt to fetch the contract using the key. Underneath, it's cached. var contract = Services.Contract.GetByKey(key.Contract) as EmitterContract; if (contract == null) { return(EmitterEventCode.NotFound); } // Check if the payment state is valid if (contract.Status == EmitterContractStatus.Refused) { return(EmitterEventCode.PaymentRequired); } // Validate the contract if (!contract.Validate(ref key)) { return(EmitterEventCode.Unauthorized); } // Check if the key has the permission to read here if (!key.HasPermission(SecurityAccess.Read)) { return(EmitterEventCode.Unauthorized); } // Check if the key has the permission for the required channel if (key.Target != 0 && info.Target != key.Target) { return(EmitterEventCode.Unauthorized); } // Subscribe to the channel var subs = Dispatcher.Subscribe(client, key.Contract, info.Channel, SubscriptionInterest.Messages); // Check if the history was also requested and we have the permission to do so var last = 0; if (!info.RequestedLast(out last) || !key.HasPermission(SecurityAccess.Load)) { return(EmitterEventCode.Success); } // Get the ssid var ssid = EmitterChannel.Ssid(key.Contract, info.Channel); // Stream the history Services.Storage .GetLastAsync(key.Contract, ssid, last) .ContinueWith(async(t) => { // Now send each message in order var stream = t.Result; while (stream.HasNext) { // Get the message asyncronously var item = await stream.GetNext(); if (item.Count == 0) { continue; } // Increment the counter Service.MessageSent?.Invoke(contract, channel, item.Count); // Send the message out var msg = MqttPublishPacket.Acquire(); msg.Channel = info.Channel; msg.Message = item; client.Send(msg); } }); // We have successfully subscribed return(EmitterEventCode.Success); } catch (NotImplementedException) { // We've got a not implemented exception return(EmitterEventCode.NotImplemented); } catch (Exception ex) { // We need to log it Service.Logger.Log(ex); // We've got a an internal error return(EmitterEventCode.ServerError); } }
/// <summary> /// Attempts to generate the key and returns the result. /// </summary> /// <param name="client">The remote client.</param> /// <param name="channel">The full channel string.</param> /// <param name="message">The message to publish.</param> public static EmitterResponse Process(IClient client, EmitterChannel channel, PresenceRequest request) { // Parse the channel EmitterChannel info; if (!EmitterChannel.TryParse(request.Channel, false, out info)) { return(EmitterError.BadRequest); } // Should be a static (non-wildcard) channel string. if (info.Type != ChannelType.Static) { return(EmitterError.BadRequest); } // Attempt to parse the key, this should be a master key SecurityKey channelKey; if (!SecurityKey.TryParse(request.Key, out channelKey) || !channelKey.HasPermission(SecurityAccess.Presence) || channelKey.IsExpired) { return(EmitterError.Unauthorized); } // Make sure the channel name ends with if (!request.Channel.EndsWith("/")) { request.Channel += "/"; } // Subscription Subscription sub; if (request.Changes) { // If we requested changes, register a subscription with this new interest Subscription.Register(client, channelKey.Contract, request.Channel, SubscriptionInterest.Presence); } else { // If the changes flag is set to false, unregister the client from presence Subscription.Unregister(client, channelKey.Contract, request.Channel, SubscriptionInterest.Presence, out sub); } // If we didn't request a presence, send an OK response only if (!request.Status) { return(new EmitterResponse(200)); } // Find the subscription if (!Subscription.TryGet(channelKey.Contract, request.Channel, out sub)) { // No subscription found, return an empty one return(new PresenceResponse(request.Channel, 0, null)); } // Current list var presenceList = sub.Presence .Where(p => !p.Deleted) .Take(1000) .Select(p => p.Value.AsInfo()) .ToList(); // If we requested a presence, prepare a response return(new PresenceResponse(request.Channel, sub.Occupancy, presenceList)); }
/// <summary> /// Periodically publishes the usage monitors. /// </summary> private void Publish() { try { // Prepare options var ourContract = SecurityLicense.Current.Contract; var builder = new StringBuilder(); // Publish every monitor as a separate message foreach (var c in Services.Contract) { // Get the usage from the contract var contract = c as EmitterContract; if (contract == null) { continue; } // If the monitor didn't change, we don't need to publish it var usage = contract.Usage; if (usage.IsZero) { continue; } try { // Reuse the builder builder.Clear(); // Sample to the througput limiter usage.MessageFrequency.Sample(usage.IncomingMessages.Value + usage.OutgoingMessages.Value); // Write and reset the usage usage.Write(builder, contract.Oid); usage.Reset(); // Convert to a message, TODO: remove unnecessary allocations var message = builder .ToString() .AsASCII() .AsSegment(); // The channel to publish into var channel = "usage/" + contract.Oid + "/"; // Get the channel hash for query EmitterChannel info; if (!EmitterChannel.TryParse(channel, false, out info)) { return; } // Publish the message in the cluster, don't need to store it as it might slow everything down Dispatcher.Publish(ourContract, info.Target, channel, message, 60); } catch (Exception ex) { // Log the exception Service.Logger.Log(ex); } } } catch (Exception ex) { // Log the exception Service.Logger.Log(ex); } }
/// <summary> /// Occurs when the remote client attempts to unsubscribe from a hub. /// </summary> /// <param name="client">The remote client.</param> /// <param name="channel">The full channel string.</param> public static EmitterEventCode Process(IClient client, string channel) { try { // Parse the channel EmitterChannel info; if (!EmitterChannel.TryParse(channel, true, out info)) { return(EmitterEventCode.BadRequest); } // Attempt to parse the key SecurityKey key; if (!SecurityKey.TryParse(info.Key, out key)) { return(EmitterEventCode.BadRequest); } // Has the key expired? if (key.IsExpired) { return(EmitterEventCode.Unauthorized); } // Have we already unsubscribed? //if (client[channel] == null) // return EmitterEventCode.Success; // Attempt to fetch the contract using the key. Underneath, it's cached. var contract = Services.Contract.GetByKey(key.Contract) as EmitterContract; if (contract == null) { return(EmitterEventCode.NotFound); } // Check if the payment state is valid if (contract.Status == EmitterContractStatus.Refused) { return(EmitterEventCode.PaymentRequired); } // Validate the contract if (!contract.Validate(ref key)) { return(EmitterEventCode.Unauthorized); } // Check if the key has the permission to read here if (!key.HasPermission(SecurityAccess.Read)) { return(EmitterEventCode.Unauthorized); } // Check if the key has the permission for the required channel if (info.Target != key.Target) { return(EmitterEventCode.Unauthorized); } // Get the subscription id from the storage if (!Dispatcher.Unsubscribe(client, key.Contract, info.Channel, SubscriptionInterest.Messages)) { return(EmitterEventCode.Unauthorized); } // Successfully unsubscribed return(EmitterEventCode.Success); } catch (NotImplementedException) { // We've got a not implemented exception return(EmitterEventCode.NotImplemented); } catch (Exception ex) { // We need to log it Service.Logger.Log(ex); // We've got a an internal error return(EmitterEventCode.ServerError); } }