mirror of https://github.com/jetkvm/kvm.git
Refactoring: Move most audio business logic into the audio package
This commit is contained in:
parent
0a38451c95
commit
2c2f2d416b
|
@ -2,7 +2,7 @@ package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"net/http"
|
||||||
|
|
||||||
"github.com/coder/websocket"
|
"github.com/coder/websocket"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -10,6 +10,15 @@ import (
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var audioControlService *audio.AudioControlService
|
||||||
|
|
||||||
|
func initAudioControlService() {
|
||||||
|
if audioControlService == nil {
|
||||||
|
sessionProvider := &SessionProviderImpl{}
|
||||||
|
audioControlService = audio.NewAudioControlService(sessionProvider, logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// handleAudioMute handles POST /audio/mute requests
|
// handleAudioMute handles POST /audio/mute requests
|
||||||
func handleAudioMute(c *gin.Context) {
|
func handleAudioMute(c *gin.Context) {
|
||||||
type muteReq struct {
|
type muteReq struct {
|
||||||
|
@ -20,13 +29,13 @@ func handleAudioMute(c *gin.Context) {
|
||||||
c.JSON(400, gin.H{"error": "invalid request"})
|
c.JSON(400, gin.H{"error": "invalid request"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
audio.SetAudioMuted(req.Muted)
|
initAudioControlService()
|
||||||
// Also set relay mute state if in main process
|
|
||||||
audio.SetAudioRelayMuted(req.Muted)
|
|
||||||
|
|
||||||
// Broadcast audio mute state change via WebSocket
|
err := audioControlService.MuteAudio(req.Muted)
|
||||||
broadcaster := audio.GetAudioEventBroadcaster()
|
if err != nil {
|
||||||
broadcaster.BroadcastAudioDeviceChanged(!req.Muted, "audio_mute_changed")
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": "audio mute state updated",
|
"status": "audio mute state updated",
|
||||||
|
@ -36,35 +45,15 @@ func handleAudioMute(c *gin.Context) {
|
||||||
|
|
||||||
// handleMicrophoneStart handles POST /microphone/start requests
|
// handleMicrophoneStart handles POST /microphone/start requests
|
||||||
func handleMicrophoneStart(c *gin.Context) {
|
func handleMicrophoneStart(c *gin.Context) {
|
||||||
if currentSession == nil {
|
initAudioControlService()
|
||||||
c.JSON(400, gin.H{"error": "no active session"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentSession.AudioInputManager == nil {
|
err := audioControlService.StartMicrophone()
|
||||||
c.JSON(500, gin.H{"error": "audio input manager not available"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cooldown using atomic operations
|
|
||||||
// Note: Cooldown check would be implemented in audio package if needed
|
|
||||||
|
|
||||||
logger.Info().Msg("starting microphone via HTTP request")
|
|
||||||
|
|
||||||
err := currentSession.AudioInputManager.Start()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast microphone state change via WebSocket
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
broadcaster := audio.GetAudioEventBroadcaster()
|
|
||||||
broadcaster.BroadcastAudioDeviceChanged(true, "microphone_started")
|
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
|
||||||
"status": "microphone started",
|
|
||||||
"is_running": currentSession.AudioInputManager.IsRunning(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleMicrophoneMute handles POST /microphone/mute requests
|
// handleMicrophoneMute handles POST /microphone/mute requests
|
||||||
|
@ -74,51 +63,32 @@ func handleMicrophoneMute(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(400, gin.H{"error": "invalid request body"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Microphone muting is typically handled at the frontend level
|
initAudioControlService()
|
||||||
// This endpoint is provided for consistency but doesn't affect backend processing
|
|
||||||
c.JSON(200, gin.H{
|
err := audioControlService.MuteMicrophone(req.Muted)
|
||||||
"status": "mute state updated",
|
if err != nil {
|
||||||
"muted": req.Muted,
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
})
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
|
||||||
// handleMicrophoneReset handles POST /microphone/reset requests
|
// handleMicrophoneReset handles POST /microphone/reset requests
|
||||||
func handleMicrophoneReset(c *gin.Context) {
|
func handleMicrophoneReset(c *gin.Context) {
|
||||||
if currentSession == nil {
|
initAudioControlService()
|
||||||
c.JSON(400, gin.H{"error": "no active session"})
|
|
||||||
|
err := audioControlService.ResetMicrophone()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentSession.AudioInputManager == nil {
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
c.JSON(500, gin.H{"error": "audio input manager not available"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cooldown using atomic operations
|
|
||||||
// Note: Cooldown check would be implemented in audio package if needed
|
|
||||||
|
|
||||||
logger.Info().Msg("forcing microphone state reset")
|
|
||||||
|
|
||||||
// Force stop the AudioInputManager
|
|
||||||
currentSession.AudioInputManager.Stop()
|
|
||||||
|
|
||||||
// Wait a bit to ensure everything is stopped
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
// Broadcast microphone state change via WebSocket
|
|
||||||
broadcaster := audio.GetAudioEventBroadcaster()
|
|
||||||
broadcaster.BroadcastAudioDeviceChanged(false, "microphone_reset")
|
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
|
||||||
"status": "microphone reset completed",
|
|
||||||
"is_running": currentSession.AudioInputManager.IsRunning(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleSubscribeAudioEvents handles WebSocket audio event subscription
|
// handleSubscribeAudioEvents handles WebSocket audio event subscription
|
||||||
|
@ -134,3 +104,63 @@ func handleUnsubscribeAudioEvents(connectionID string, l *zerolog.Logger) {
|
||||||
broadcaster := audio.GetAudioEventBroadcaster()
|
broadcaster := audio.GetAudioEventBroadcaster()
|
||||||
broadcaster.Unsubscribe(connectionID)
|
broadcaster.Unsubscribe(connectionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleAudioQuality handles GET requests for audio quality presets
|
||||||
|
func handleAudioQuality(c *gin.Context) {
|
||||||
|
presets := audio.GetAudioQualityPresets()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"presets": presets,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSetAudioQuality handles POST requests to set audio quality
|
||||||
|
func handleSetAudioQuality(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Quality int `json:"quality"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initAudioControlService()
|
||||||
|
|
||||||
|
// Convert int to AudioQuality type
|
||||||
|
quality := audio.AudioQuality(req.Quality)
|
||||||
|
|
||||||
|
// Set the audio quality
|
||||||
|
audioControlService.SetAudioQuality(quality)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMicrophoneQuality handles GET requests for microphone quality presets
|
||||||
|
func handleMicrophoneQuality(c *gin.Context) {
|
||||||
|
presets := audio.GetMicrophoneQualityPresets()
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"presets": presets,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSetMicrophoneQuality handles POST requests to set microphone quality
|
||||||
|
func handleSetMicrophoneQuality(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Quality int `json:"quality"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initAudioControlService()
|
||||||
|
|
||||||
|
// Convert int to AudioQuality type
|
||||||
|
quality := audio.AudioQuality(req.Quality)
|
||||||
|
|
||||||
|
// Set the microphone quality
|
||||||
|
audioControlService.SetMicrophoneQuality(quality)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package kvm
|
||||||
|
|
||||||
|
import "github.com/jetkvm/kvm/internal/audio"
|
||||||
|
|
||||||
|
// SessionProviderImpl implements the audio.SessionProvider interface
|
||||||
|
type SessionProviderImpl struct{}
|
||||||
|
|
||||||
|
// NewSessionProvider creates a new session provider
|
||||||
|
func NewSessionProvider() *SessionProviderImpl {
|
||||||
|
return &SessionProviderImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSessionActive returns whether there's an active session
|
||||||
|
func (sp *SessionProviderImpl) IsSessionActive() bool {
|
||||||
|
return currentSession != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAudioInputManager returns the current session's audio input manager
|
||||||
|
func (sp *SessionProviderImpl) GetAudioInputManager() *audio.AudioInputManager {
|
||||||
|
if currentSession == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return currentSession.AudioInputManager
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
package audio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/coder/websocket"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AudioControlService provides core audio control operations
|
||||||
|
type AudioControlService struct {
|
||||||
|
sessionProvider SessionProvider
|
||||||
|
logger *zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAudioControlService creates a new audio control service
|
||||||
|
func NewAudioControlService(sessionProvider SessionProvider, logger *zerolog.Logger) *AudioControlService {
|
||||||
|
return &AudioControlService{
|
||||||
|
sessionProvider: sessionProvider,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuteAudio sets the audio mute state
|
||||||
|
func (s *AudioControlService) MuteAudio(muted bool) error {
|
||||||
|
SetAudioMuted(muted)
|
||||||
|
SetAudioRelayMuted(muted)
|
||||||
|
|
||||||
|
// Broadcast audio mute state change via WebSocket
|
||||||
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
|
broadcaster.BroadcastAudioDeviceChanged(!muted, "audio_mute_changed")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartMicrophone starts the microphone input
|
||||||
|
func (s *AudioControlService) StartMicrophone() error {
|
||||||
|
if !s.sessionProvider.IsSessionActive() {
|
||||||
|
return errors.New("no active session")
|
||||||
|
}
|
||||||
|
|
||||||
|
audioInputManager := s.sessionProvider.GetAudioInputManager()
|
||||||
|
if audioInputManager == nil {
|
||||||
|
return errors.New("audio input manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if audioInputManager.IsRunning() {
|
||||||
|
s.logger.Info().Msg("microphone already running")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := audioInputManager.Start(); err != nil {
|
||||||
|
s.logger.Error().Err(err).Msg("failed to start microphone")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info().Msg("microphone started successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuteMicrophone sets the microphone mute state
|
||||||
|
func (s *AudioControlService) MuteMicrophone(muted bool) error {
|
||||||
|
// Set microphone mute state using the audio relay
|
||||||
|
SetAudioRelayMuted(muted)
|
||||||
|
|
||||||
|
// Broadcast microphone mute state change via WebSocket
|
||||||
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
|
broadcaster.BroadcastAudioDeviceChanged(!muted, "microphone_mute_changed")
|
||||||
|
|
||||||
|
s.logger.Info().Bool("muted", muted).Msg("microphone mute state updated")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetMicrophone resets the microphone
|
||||||
|
func (s *AudioControlService) ResetMicrophone() error {
|
||||||
|
if !s.sessionProvider.IsSessionActive() {
|
||||||
|
return errors.New("no active session")
|
||||||
|
}
|
||||||
|
|
||||||
|
audioInputManager := s.sessionProvider.GetAudioInputManager()
|
||||||
|
if audioInputManager == nil {
|
||||||
|
return errors.New("audio input manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if audioInputManager.IsRunning() {
|
||||||
|
audioInputManager.Stop()
|
||||||
|
s.logger.Info().Msg("stopped microphone for reset")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := audioInputManager.Start(); err != nil {
|
||||||
|
s.logger.Error().Err(err).Msg("failed to restart microphone during reset")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info().Msg("microphone reset successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMicrophoneStatus returns the current microphone status
|
||||||
|
func (s *AudioControlService) GetMicrophoneStatus() map[string]interface{} {
|
||||||
|
if s.sessionProvider == nil {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"error": "no session provider",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.sessionProvider.IsSessionActive() {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"error": "no active session",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audioInputManager := s.sessionProvider.GetAudioInputManager()
|
||||||
|
if audioInputManager == nil {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"error": "no audio input manager",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"running": audioInputManager.IsRunning(),
|
||||||
|
"ready": audioInputManager.IsReady(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAudioQuality sets the audio output quality
|
||||||
|
func (s *AudioControlService) SetAudioQuality(quality AudioQuality) {
|
||||||
|
SetAudioQuality(quality)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMicrophoneQuality sets the microphone input quality
|
||||||
|
func (s *AudioControlService) SetMicrophoneQuality(quality AudioQuality) {
|
||||||
|
SetMicrophoneQuality(quality)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAudioQualityPresets returns available audio quality presets
|
||||||
|
func (s *AudioControlService) GetAudioQualityPresets() map[AudioQuality]AudioConfig {
|
||||||
|
return GetAudioQualityPresets()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMicrophoneQualityPresets returns available microphone quality presets
|
||||||
|
func (s *AudioControlService) GetMicrophoneQualityPresets() map[AudioQuality]AudioConfig {
|
||||||
|
return GetMicrophoneQualityPresets()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeToAudioEvents subscribes to audio events via WebSocket
|
||||||
|
func (s *AudioControlService) SubscribeToAudioEvents(connectionID string, wsCon *websocket.Conn, runCtx context.Context, logger *zerolog.Logger) {
|
||||||
|
logger.Info().Msg("client subscribing to audio events")
|
||||||
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
|
broadcaster.Subscribe(connectionID, wsCon, runCtx, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsubscribeFromAudioEvents unsubscribes from audio events
|
||||||
|
func (s *AudioControlService) UnsubscribeFromAudioEvents(connectionID string, logger *zerolog.Logger) {
|
||||||
|
logger.Info().Str("connection_id", connectionID).Msg("client unsubscribing from audio events")
|
||||||
|
broadcaster := GetAudioEventBroadcaster()
|
||||||
|
broadcaster.Unsubscribe(connectionID)
|
||||||
|
}
|
4
web.go
4
web.go
|
@ -157,6 +157,10 @@ func setupRouter() *gin.Engine {
|
||||||
|
|
||||||
// Audio handlers
|
// Audio handlers
|
||||||
protected.POST("/audio/mute", handleAudioMute)
|
protected.POST("/audio/mute", handleAudioMute)
|
||||||
|
protected.GET("/audio/quality", handleAudioQuality)
|
||||||
|
protected.POST("/audio/quality", handleSetAudioQuality)
|
||||||
|
protected.GET("/microphone/quality", handleMicrophoneQuality)
|
||||||
|
protected.POST("/microphone/quality", handleSetMicrophoneQuality)
|
||||||
protected.POST("/microphone/start", handleMicrophoneStart)
|
protected.POST("/microphone/start", handleMicrophoneStart)
|
||||||
protected.POST("/microphone/mute", handleMicrophoneMute)
|
protected.POST("/microphone/mute", handleMicrophoneMute)
|
||||||
protected.POST("/microphone/reset", handleMicrophoneReset)
|
protected.POST("/microphone/reset", handleMicrophoneReset)
|
||||||
|
|
Loading…
Reference in New Issue