package audio import ( "sync/atomic" "time" "github.com/jetkvm/kvm/internal/logging" "github.com/rs/zerolog" ) // AudioOutputManager manages audio output stream using IPC mode type AudioOutputManager struct { metrics AudioOutputMetrics streamer *AudioOutputStreamer logger zerolog.Logger running int32 } // AudioOutputMetrics tracks output-specific metrics type AudioOutputMetrics struct { FramesReceived int64 FramesDropped int64 BytesProcessed int64 ConnectionDrops int64 LastFrameTime time.Time AverageLatency time.Duration } // NewAudioOutputManager creates a new audio output manager func NewAudioOutputManager() *AudioOutputManager { streamer, err := NewAudioOutputStreamer() if err != nil { // Log error but continue with nil streamer - will be handled gracefully logger := logging.GetDefaultLogger().With().Str("component", AudioOutputManagerComponent).Logger() logger.Error().Err(err).Msg("Failed to create audio output streamer") } return &AudioOutputManager{ streamer: streamer, logger: logging.GetDefaultLogger().With().Str("component", AudioOutputManagerComponent).Logger(), } } // Start starts the audio output manager func (aom *AudioOutputManager) Start() error { if !atomic.CompareAndSwapInt32(&aom.running, 0, 1) { return nil // Already running } aom.logger.Info().Str("component", AudioOutputManagerComponent).Msg("starting component") if aom.streamer == nil { // Try to recreate streamer if it was nil streamer, err := NewAudioOutputStreamer() if err != nil { atomic.StoreInt32(&aom.running, 0) aom.logger.Error().Err(err).Str("component", AudioOutputManagerComponent).Msg("failed to create audio output streamer") return err } aom.streamer = streamer } err := aom.streamer.Start() if err != nil { atomic.StoreInt32(&aom.running, 0) // Reset metrics on failed start aom.resetMetrics() aom.logger.Error().Err(err).Str("component", AudioOutputManagerComponent).Msg("failed to start component") return err } aom.logger.Info().Str("component", AudioOutputManagerComponent).Msg("component started successfully") return nil } // Stop stops the audio output manager func (aom *AudioOutputManager) Stop() { if !atomic.CompareAndSwapInt32(&aom.running, 1, 0) { return // Already stopped } aom.logger.Info().Str("component", AudioOutputManagerComponent).Msg("stopping component") if aom.streamer != nil { aom.streamer.Stop() } aom.logger.Info().Str("component", AudioOutputManagerComponent).Msg("component stopped") } // resetMetrics resets all metrics to zero func (aom *AudioOutputManager) resetMetrics() { atomic.StoreInt64(&aom.metrics.FramesReceived, 0) atomic.StoreInt64(&aom.metrics.FramesDropped, 0) atomic.StoreInt64(&aom.metrics.BytesProcessed, 0) atomic.StoreInt64(&aom.metrics.ConnectionDrops, 0) } // IsRunning returns whether the audio output manager is running func (aom *AudioOutputManager) IsRunning() bool { return atomic.LoadInt32(&aom.running) == 1 } // IsReady returns whether the audio output manager is ready to receive frames func (aom *AudioOutputManager) IsReady() bool { if !aom.IsRunning() || aom.streamer == nil { return false } // For output, we consider it ready if the streamer is running // This could be enhanced with connection status checks return true } // GetMetrics returns current metrics func (aom *AudioOutputManager) GetMetrics() AudioOutputMetrics { return AudioOutputMetrics{ FramesReceived: atomic.LoadInt64(&aom.metrics.FramesReceived), FramesDropped: atomic.LoadInt64(&aom.metrics.FramesDropped), BytesProcessed: atomic.LoadInt64(&aom.metrics.BytesProcessed), ConnectionDrops: atomic.LoadInt64(&aom.metrics.ConnectionDrops), AverageLatency: aom.metrics.AverageLatency, LastFrameTime: aom.metrics.LastFrameTime, } } // GetComprehensiveMetrics returns detailed performance metrics func (aom *AudioOutputManager) GetComprehensiveMetrics() map[string]interface{} { baseMetrics := aom.GetMetrics() comprehensiveMetrics := map[string]interface{}{ "manager": map[string]interface{}{ "frames_received": baseMetrics.FramesReceived, "frames_dropped": baseMetrics.FramesDropped, "bytes_processed": baseMetrics.BytesProcessed, "connection_drops": baseMetrics.ConnectionDrops, "average_latency_ms": float64(baseMetrics.AverageLatency.Nanoseconds()) / 1e6, "last_frame_time": baseMetrics.LastFrameTime, "running": aom.IsRunning(), "ready": aom.IsReady(), }, } if aom.streamer != nil { processed, dropped, avgTime := aom.streamer.GetStats() comprehensiveMetrics["streamer"] = map[string]interface{}{ "frames_processed": processed, "frames_dropped": dropped, "avg_processing_time_ms": float64(avgTime.Nanoseconds()) / 1e6, } if detailedStats := aom.streamer.GetDetailedStats(); detailedStats != nil { comprehensiveMetrics["detailed"] = detailedStats } } return comprehensiveMetrics } // LogPerformanceStats logs current performance statistics func (aom *AudioOutputManager) LogPerformanceStats() { metrics := aom.GetMetrics() aom.logger.Info(). Int64("frames_received", metrics.FramesReceived). Int64("frames_dropped", metrics.FramesDropped). Int64("bytes_processed", metrics.BytesProcessed). Int64("connection_drops", metrics.ConnectionDrops). Float64("average_latency_ms", float64(metrics.AverageLatency.Nanoseconds())/1e6). Bool("running", aom.IsRunning()). Bool("ready", aom.IsReady()). Msg("Audio output manager performance stats") } // GetStreamer returns the streamer for advanced operations func (aom *AudioOutputManager) GetStreamer() *AudioOutputStreamer { return aom.streamer }