public void Update(bool muted, float deltaTime)
        {
            //Delay the initial startup of the capture pipeline. This spreads out the cost of initialising Dissonance over more frames, preventing very large spikes caused by everything being set up at once
            _startupDelay--;
            if (_startupDelay > 0)
            {
                return;
            }

            _receivingPacketLossMonitor.Update();

            //Early exit if we don't need to record audio. Either because:
            // - Netmode doesn't require audio (i.e. dedicated server)
            // - A fatal exception occurred, permanently killing capture
            if (!_netModeRequiresPipeline || _encounteredFatalException || _cannotStartMic)
            {
                StopTransmissionPipeline();
                return;
            }

            //Update microphone and reset it either if there is a frame skip or the microphone requests a reset
            var skipped = _skipDetector.IsFrameSkip(deltaTime);
            var request = _microphone.IsRecording && _microphone.UpdateSubscribers();
            var netmode = _netModeRequiresPipeline && _encoder == null;

            if (skipped || request || _pendingResetRequest || netmode)
            {
                var reason = skipped ? "Detected a frame skip, forcing capture pipeline reset"
                             : netmode ? "Network mode changed"
                             : _pendingResetRequest ? "Applying external reset request"
                             : "Microphone requested a pipeline reset";

                if (skipped)
                {
                    //If warn level logging is turned on show some extra information in the reason (at the cost of a string allocation)
                    if (Log.IsWarn)
                    {
                        reason = string.Format("Detected a frame skip, forcing capture pipeline reset (Delta Time:{0})", deltaTime);
                    }
                    Log.Warn(reason);
                }

                RestartTransmissionPipeline(reason);
            }

            if (_encoder != null)
            {
                //If the encoder is finally stopped and still subscribed then unsubscribe and reset it. This puts it into a state ready to be used again.
                if (_encoder.Stopped && _encoderSubscribed)
                {
                    Log.Debug("Unsubscribing encoder from preprocessor");

                    _preprocessor.Unsubscribe(_encoder);
                    _encoder.Reset();
                    _encoderSubscribed = false;
                }

                //Determine if the encoder should be subscribed:
                // - If encoder is stopping (but has not yet stopped) then do not sub
                // - If mute is explicitly set then do not sub
                // - if there are open channels then sub
                var shouldSub = !(_encoder.Stopping && !_encoder.Stopped) &&
                                !muted &&
                                (_roomChannels.Count + _playerChannels.Count) > 0;

                //Change the encoder state to the desired state
                if (shouldSub != _encoderSubscribed)
                {
                    if (shouldSub)
                    {
                        Log.Debug("Subscribing encoder to preprocessor");

                        _encoder.Reset();
                        _preprocessor.Subscribe(_encoder);
                        _encoderSubscribed = true;
                    }
                    else
                    {
                        //If the encoder has not been told to stop, tell it now
                        if (!_encoder.Stopping)
                        {
                            //Set the encoder state to stopping - it will stop after it sends one final packet to end the stream
                            _encoder.Stop();
                            Log.Debug("Stopping encoder");
                        }
                        else
                        {
                            Log.Debug("Waiting for encoder to send last packet");
                        }
                    }
                }
                else
                {
                    //Log.Debug("Should Sub - Stopping:{0} Stopped:{1} Muted:{1}", _encoder.Stopping, _encoder.Stopped, muted);
                }

                //Propogate measured *incoming* packet loss to encoder as expected *outgoing* packet loss
                if (_encoder != null)
                {
                    _encoder.TransmissionPacketLoss = _receivingPacketLossMonitor.PacketLoss;
                }
            }
        }
        public void Update(bool muted, float deltaTime)
        {
            _receivingPacketLossMonitor.Update();

            //Update microphone and reset it either if there is a frame skip or the microphone requests a reset
            var skipped = _skipDetector.IsFrameSkip(deltaTime);
            var request = _microphone.IsRecording && _microphone.UpdateSubscribers();

            if (skipped || request)
            {
                if (skipped)
                {
                    Log.Warn("Detected a frame skip, forcing capture pipeline reset");
                }

                RestartTransmissionPipeline();
            }

            if (_encoder != null)
            {
                //If the encoder is finally stopped and still subscribed then unsubscribe and reset it. This puts it into a state ready to be used again.
                if (_encoder.Stopped && _encoderSubscribed)
                {
                    Log.Debug("Unsubscribing encoder from preprocessor");

                    _preprocessor.Unsubscribe(_encoder);
                    _encoder.Reset();
                    _encoderSubscribed = false;
                }

                //Check if the encoder state matches the desired state
                var shouldSub = !(_encoder.Stopping && !_encoder.Stopped) && !muted && (_roomChannels.Count + _playerChannels.Count) > 0;
                if (shouldSub != _encoderSubscribed)
                {
                    if (shouldSub)
                    {
                        Log.Debug("Subscribing encoder to preprocessor");

                        _encoder.Reset();
                        _preprocessor.Subscribe(_encoder);
                        _encoderSubscribed = true;
                    }
                    else
                    {
                        //Inform the encoder that it should stop encoding (after sending one final packet)
                        if (!_encoder.Stopping)
                        {
                            _encoder.Stop();
                            Log.Debug("Stopping encoder");
                        }
                        else
                        {
                            Log.Debug("Waiting for encoder to send last packet");
                        }
                    }
                }
                else
                {
                    //Log.Debug("Should Sub - Stopping:{0} Muted:{1}", _encoder.Stopping, muted);
                }

                //Propogate measured *incoming* packet loss to encoder as expected *outgoing* packet loss
                if (_encoder != null)
                {
                    _encoder.TransmissionPacketLoss = _receivingPacketLossMonitor.PacketLoss;
                }
            }
        }