diff options
| author | Bobby <[email protected]> | 2025-04-13 18:41:17 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-04-13 18:41:17 +0530 |
| commit | 6b486844a18fe8bf9b4ca4bdd3c44f45094e03e5 (patch) | |
| tree | b2655928e1fd60516619359ba214015084b05b9c | |
| parent | f884a0fe47f7403850de650d3be3733420ac5986 (diff) | |
| download | ai-6b486844a18fe8bf9b4ca4bdd3c44f45094e03e5.tar.xz ai-6b486844a18fe8bf9b4ca4bdd3c44f45094e03e5.zip | |
refactor; add logging; add disconnect; supress opus warnings
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | commands/commands.go | 4 | ||||
| -rw-r--r-- | commands/disconnect.go | 55 | ||||
| -rw-r--r-- | commands/helpers.go | 19 | ||||
| -rw-r--r-- | commands/play.go | 31 | ||||
| -rw-r--r-- | commands/play_autocomplete.go | 8 | ||||
| -rw-r--r-- | handlers/slashCommandHandler.go | 3 | ||||
| -rw-r--r-- | utils/music/search.go | 25 | ||||
| -rw-r--r-- | utils/music/voice.go | 274 |
9 files changed, 215 insertions, 206 deletions
@@ -38,7 +38,7 @@ run: dev: $(call ensure_setup) @echo "Running in development mode..." - @go run $(MAIN_PATH) || true + @go run $(MAIN_PATH) 2>/dev/null || true all: setup clean build run diff --git a/commands/commands.go b/commands/commands.go index 00f09f8..756c38f 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -17,5 +17,9 @@ var ( }, }, }, + { + Name: "disconnect", + Description: "Disconnect the bot from the voice channel", + }, } ) diff --git a/commands/disconnect.go b/commands/disconnect.go new file mode 100644 index 0000000..94249c8 --- /dev/null +++ b/commands/disconnect.go @@ -0,0 +1,55 @@ +package commands + +import ( + "ai/utils/music" + "fmt" + + "github.com/bwmarrin/discordgo" +) + +func Disconnect(s *discordgo.Session, i *discordgo.InteractionCreate) { + guildID := i.GuildID + userID := i.Member.User.ID + + isSameVC, userChannelID := music.IsUserInSameVC(s, guildID, userID) + + if userChannelID == "" { + respondWithError(s, i, "You must be in a voice channel to use this command.") + return + } + + voice, exists := music.VoiceConnection[guildID] + if !exists { + respondWithError(s, i, "I'm not in a voice channel.") + return + } + + if !isSameVC { + channel, err := s.Channel(voice.ChannelID) + if err == nil { + respondWithError(s, i, fmt.Sprintf("You must be in the same voice channel as me (**%s**) to use this command.", channel.Name)) + } else { + respondWithError(s, i, "You must be in the same voice channel as me to use this command.") + } + return + } + + channel, err := s.Channel(voice.ChannelID) + channelName := "voice channel" + if err == nil { + channelName = channel.Name + } + + err = music.LeaveVoiceChannel(guildID) + if err != nil { + respondWithError(s, i, fmt.Sprintf("Error disconnecting from voice channel: %v", err)) + return + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("✅ Disconnected from **%s**.", channelName), + }, + }) +} diff --git a/commands/helpers.go b/commands/helpers.go new file mode 100644 index 0000000..19859db --- /dev/null +++ b/commands/helpers.go @@ -0,0 +1,19 @@ +package commands + +import "github.com/bwmarrin/discordgo" + +func respondWithError(s *discordgo.Session, i *discordgo.InteractionCreate, message string) { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: message, + Flags: discordgo.MessageFlagsEphemeral, + }, + }) +} + +func updateResponse(s *discordgo.Session, i *discordgo.InteractionCreate, message string) { + s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ + Content: &message, + }) +} diff --git a/commands/play.go b/commands/play.go index b8d39fd..6aac6c3 100644 --- a/commands/play.go +++ b/commands/play.go @@ -14,7 +14,6 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { options := i.ApplicationCommandData().Options input := options[0].StringValue() - // Special cases from autocomplete if input == "min_chars" { respondWithError(s, i, "Enter at least 3 characters to search.") return @@ -25,7 +24,6 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Check if the user is in a voice channel BEFORE deferring response guildID := i.GuildID userID := i.Member.User.ID @@ -36,7 +34,6 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Check if bot is already in a voice channel but not the same as the user voice, exists := music.VoiceConnection[guildID] if exists && !isSameVC { channel, err := s.Channel(voice.ChannelID) @@ -48,12 +45,10 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Now that we've checked all error conditions, defer the response s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseDeferredChannelMessageWithSource, }) - // Process track selection or URL var trackURL, trackID, trackTitle string var sourceType types.SourceType @@ -81,12 +76,10 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { trackID = ytTrack.ID } } else { - // malformed selection updateResponse(s, i, "❌ Invalid track selection. Please try again.") return } } else { - // Direct URL or search query if music.IsYouTubeURL(input) { trackInfo, err := music.GetYouTubeInfo(input) if err != nil { @@ -104,7 +97,6 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Get YouTube equivalent ytTrack, err := music.GetYouTubeForSpotify(trackInfo.Title, trackInfo.Artist) if err != nil { updateResponse(s, i, "❌ Error fetching YouTube equivalent for Spotify track.") @@ -112,10 +104,9 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { } trackURL = ytTrack.URL trackID = ytTrack.ID - trackTitle = ytTrack.Title + trackTitle = trackInfo.Title sourceType = types.Spotify } else { - // treat as search query results, err := music.Search(input, 1) if err != nil || len(results) == 0 { updateResponse(s, i, "❌ No results found for your search query.") @@ -141,7 +132,6 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { } } - // Join voice channel voice, err := music.JoinVoiceChannel(s, guildID, userChannelID) if err != nil { logger.Log(fmt.Sprintf("Failed to join voice channel: %v", err), types.LogOptions{ @@ -152,10 +142,8 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Send "now playing" message first updateResponse(s, i, fmt.Sprintf("🎵 Now playing: **%s**", trackTitle)) - // Play the track go func() { err := voice.PlayYouTube(trackURL, trackID) if err != nil { @@ -163,24 +151,7 @@ func Play(s *discordgo.Session, i *discordgo.InteractionCreate) { Prefix: "Play Command", Level: types.Error, }) - // Also update the message to show the error to the user updateResponse(s, i, fmt.Sprintf("❌ Error playing **%s**: %v", trackTitle, err)) } }() } - -func respondWithError(s *discordgo.Session, i *discordgo.InteractionCreate, message string) { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: message, - Flags: discordgo.MessageFlagsEphemeral, - }, - }) -} - -func updateResponse(s *discordgo.Session, i *discordgo.InteractionCreate, message string) { - s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ - Content: &message, - }) -} diff --git a/commands/play_autocomplete.go b/commands/play_autocomplete.go index dee02d1..85613b3 100644 --- a/commands/play_autocomplete.go +++ b/commands/play_autocomplete.go @@ -40,7 +40,6 @@ func PlayAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Search for tracks results, err := music.Search(query, 10) if err != nil { logger.Log(fmt.Sprintf("Search error: %v", err), types.LogOptions{ @@ -62,15 +61,13 @@ func PlayAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - // Create choices for autocomplete choices := make([]*discordgo.ApplicationCommandOptionChoice, 0, 25) for i, result := range results { if i >= 25 { - break // Discord limits to 25 choices + break } - // Format display name var displayName string if result.SourceType == types.YouTube { displayName = fmt.Sprintf("▶️ %s - %s", result.Title, result.Artist) @@ -78,12 +75,10 @@ func PlayAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) { displayName = fmt.Sprintf("🎵 %s - %s", result.Title, result.Artist) } - // Truncate name if needed if len(displayName) > 100 { displayName = displayName[:97] + "..." } - // Use just source type and ID as value to avoid length issues valueStr := fmt.Sprintf("%s|%s|%s", result.SourceType, result.ID, result.URL) if len(valueStr) > 100 { valueStr = fmt.Sprintf("%s|%s", result.SourceType, result.ID) @@ -102,7 +97,6 @@ func PlayAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) { }) } - // Send response err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionApplicationCommandAutocompleteResult, Data: &discordgo.InteractionResponseData{ diff --git a/handlers/slashCommandHandler.go b/handlers/slashCommandHandler.go index ebcd0db..5739e72 100644 --- a/handlers/slashCommandHandler.go +++ b/handlers/slashCommandHandler.go @@ -8,6 +8,7 @@ import ( var ( SlashCommandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ - "play": commands.Play, + "play": commands.Play, + "disconnect": commands.Disconnect, } ) diff --git a/utils/music/search.go b/utils/music/search.go index 3cef107..5607426 100644 --- a/utils/music/search.go +++ b/utils/music/search.go @@ -3,6 +3,7 @@ package music import ( "ai/config" "ai/types" + "ai/utils/logger" "encoding/json" "fmt" "io" @@ -73,13 +74,17 @@ func Search(query string, limit int) ([]types.MusicSearchResult, error) { func SearchSpotify(query string, limit int) ([]types.MusicSearchResult, error) { token, err := getSpotifyToken() if err != nil { - return nil, fmt.Errorf("spotify token error: %w", err) + logger.Log("Spotify token error: "+err.Error(), types.LogOptions{ + Prefix: "Search", + Level: types.Error, + }) + return nil, err } searchURL := fmt.Sprintf("https://api.spotify.com/v1/search?q=%s&type=track&limit=%d", url.QueryEscape(query), limit) req, err := http.NewRequest("GET", searchURL, nil) if err != nil { - return nil, fmt.Errorf("request creation error: %w", err) + return nil, err } req.Header.Add("Authorization", "Bearer "+token) @@ -87,18 +92,18 @@ func SearchSpotify(query string, limit int) ([]types.MusicSearchResult, error) { client := &http.Client{} resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("search request error: %w", err) + return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("response read error: %w", err) + return nil, err } var searchResponse types.SpotifySearchResponse if err := json.Unmarshal(body, &searchResponse); err != nil { - return nil, fmt.Errorf("json unmarshal error: %w", err) + return nil, err } results := []types.MusicSearchResult{} @@ -114,7 +119,6 @@ func SearchSpotify(query string, limit int) ([]types.MusicSearchResult, error) { thumbnailURL = item.Album.Images[0].URL } - // Format duration as mm:ss durationSec := item.DurationMs / 1000 duration := fmt.Sprintf("%02d:%02d", durationSec/60, durationSec%60) @@ -141,18 +145,18 @@ func SearchYouTube(query string, limit int) ([]types.MusicSearchResult, error) { resp, err := http.Get(searchURL) if err != nil { - return nil, fmt.Errorf("search request error: %w", err) + return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("response read error: %w", err) + return nil, err } var searchResponse types.YouTubeSearchResponse if err := json.Unmarshal(body, &searchResponse); err != nil { - return nil, fmt.Errorf("json unmarshal error: %w", err) + return nil, err } results := []types.MusicSearchResult{} @@ -165,7 +169,7 @@ func SearchYouTube(query string, limit int) ([]types.MusicSearchResult, error) { Artist: item.Snippet.ChannelTitle, URL: videoURL, ID: item.ID.VideoID, - Duration: "00:00", // YouTube API requires a separate call to get duration + Duration: "00:00", Thumbnail: item.Snippet.Thumbnails.High.URL, SourceType: types.YouTube, }) @@ -357,7 +361,6 @@ func GetSpotifyInfo(spotifyURL string) (types.MusicSearchResult, error) { parts := strings.Split(spotifyURL, "/") trackID = parts[len(parts)-1] - // Remove any query parameters if strings.Contains(trackID, "?") { trackID = strings.Split(trackID, "?")[0] } diff --git a/utils/music/voice.go b/utils/music/voice.go index 961a8f5..ab0a3f2 100644 --- a/utils/music/voice.go +++ b/utils/music/voice.go @@ -1,6 +1,8 @@ package music import ( + "ai/types" + "ai/utils/logger" "encoding/binary" "fmt" "io" @@ -14,13 +16,12 @@ import ( ) const ( - channels int = 2 // 1 for mono, 2 for stereo - frameRate int = 48000 // audio sampling rate - frameSize int = 960 // size of each audio frame - maxBytes int = (frameSize * 2) * 2 // max size of opus data + channels int = 2 + frameRate int = 48000 + frameSize int = 960 + maxBytes int = (frameSize * 2) * 2 ) -// VoiceInstance represents a voice connection type VoiceInstance struct { GuildID string ChannelID string @@ -37,7 +38,6 @@ var ( VoiceMutex = &sync.Mutex{} ) -// Stop stops the current playback func (v *VoiceInstance) Stop() { v.mu.Lock() defer v.mu.Unlock() @@ -45,9 +45,7 @@ func (v *VoiceInstance) Stop() { if v.Playing { select { case v.StopChannel <- true: - // Signal sent here default: - // Channel already has a signal } close(v.StopChannel) v.StopChannel = make(chan bool, 1) @@ -55,26 +53,23 @@ func (v *VoiceInstance) Stop() { } } -// JoinVoiceChannel makes the bot join a voice channel func JoinVoiceChannel(s *discordgo.Session, guildID, channelID string) (*VoiceInstance, error) { VoiceMutex.Lock() defer VoiceMutex.Unlock() - // Check if already in a voice channel in this guild if voice, exists := VoiceConnection[guildID]; exists { return voice, nil } - // not in a voice channel, create a new one vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true) if err != nil { - return nil, fmt.Errorf("failed to join voice channel: %w", err) + return nil, err } encoder, err := gopus.NewEncoder(frameRate, channels, gopus.Audio) if err != nil { vc.Disconnect() - return nil, fmt.Errorf("failed to create opus encoder: %w", err) + return nil, err } voiceInstance := &VoiceInstance{ @@ -90,33 +85,27 @@ func JoinVoiceChannel(s *discordgo.Session, guildID, channelID string) (*VoiceIn return voiceInstance, nil } -// LeaveVoiceChannel makes the bot leave a voice channel func LeaveVoiceChannel(guildID string) error { VoiceMutex.Lock() defer VoiceMutex.Unlock() voice, exists := VoiceConnection[guildID] if !exists { - return fmt.Errorf("not in a voice channel") + return nil } - // Stop current playback voice.Stop() - // Disconnect err := voice.Connection.Disconnect() if err != nil { - return fmt.Errorf("failed to disconnect from voice channel: %w", err) + return err } - // remove from map delete(VoiceConnection, guildID) return nil } -// IsUserInSameVC checks if the user is in the same voice channel as the bot func IsUserInSameVC(s *discordgo.Session, guildID, userID string) (bool, string) { - // Voice state guild, err := s.State.Guild(guildID) if err != nil { return false, "" @@ -131,35 +120,35 @@ func IsUserInSameVC(s *discordgo.Session, guildID, userID string) (bool, string) } if userChannelID == "" { - return false, "" // user not in a voice channel + return false, "" } - // Check if bot is in a voice channel VoiceMutex.Lock() defer VoiceMutex.Unlock() voice, exists := VoiceConnection[guildID] if !exists { - return true, userChannelID // bot not in a voice channel, but no conflict + return true, userChannelID } return voice.ChannelID == userChannelID, userChannelID } -// PlayYouTube downloads and plays a YouTube video func (v *VoiceInstance) PlayYouTube(videoURL, videoID string) error { - fmt.Printf("Starting to play: %s (ID: %s)\n", videoURL, videoID) + logger.Log("Starting to play: "+videoURL, types.LogOptions{ + Prefix: "Music Player", + Level: types.Info, + }) - // Create a new stop channel for this playback var oldStopChan chan bool v.mu.Lock() - // If already playing, properly stop the previous playback if v.Playing { - fmt.Println("Stopping current playback before starting new one...") - // Save the old channel to send the stop signal after we release the lock + logger.Log("Stopping current playback", types.LogOptions{ + Prefix: "Music Player", + Level: types.Info, + }) oldStopChan = v.StopChannel - // Create a new channel for the new playback v.StopChannel = make(chan bool, 1) } else { v.StopChannel = make(chan bool, 1) @@ -170,130 +159,165 @@ func (v *VoiceInstance) PlayYouTube(videoURL, videoID string) error { stopChan := v.StopChannel v.mu.Unlock() - // Send stop signal to old channel if it exists - // Do this outside the lock to avoid deadlock if oldStopChan != nil { - // Signal the old playback to stop select { case oldStopChan <- true: - fmt.Println("Stop signal sent to previous playback") + logger.Log("Stop signal sent to previous playback", types.LogOptions{ + Prefix: "Music Player", + Level: types.Debug, + }) default: - fmt.Println("Could not send stop signal, channel might be full or closed") + logger.Log("Could not send stop signal", types.LogOptions{ + Prefix: "Music Player", + Level: types.Debug, + }) } - // Wait a moment for the previous playback to clean up time.Sleep(100 * time.Millisecond) } - // Ensure temp directory exists err := os.MkdirAll("./temp", 0755) if err != nil { - fmt.Printf("Error creating temp directory: %v\n", err) - return fmt.Errorf("failed to create temp directory: %w", err) + logger.Log("Failed to create temp directory: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) + return err } - // Create a unique filename fileName := fmt.Sprintf("./temp/%s_%d.mp3", videoID, time.Now().Unix()) - fmt.Printf("Downloading to: %s\n", fileName) + logger.Log("Downloading to: "+fileName, types.LogOptions{ + Prefix: "Music Player", + Level: types.Debug, + }) - // Use yt-dlp to download audio - downloadCmd := exec.Command("yt-dlp", "-x", "--audio-format", "mp3", + downloadCmd := exec.Command("yt-dlp", "--no-warnings", "--quiet", "-x", "--audio-format", "mp3", "--audio-quality", "0", "--no-playlist", "--output", fileName, videoURL) - // Set up pipes to capture output for debugging - downloadCmd.Stdout = os.Stdout - downloadCmd.Stderr = os.Stderr + // Completely suppress output + devNull, _ := os.OpenFile(os.DevNull, os.O_WRONLY, 0) + defer devNull.Close() + downloadCmd.Stdout = devNull + downloadCmd.Stderr = devNull + + logger.Log("Starting download", types.LogOptions{ + Prefix: "Music Player", + Level: types.Debug, + }) - fmt.Println("Starting download...") err = downloadCmd.Run() if err != nil { - fmt.Printf("Download error: %v\n", err) + logger.Log("Download error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) v.mu.Lock() v.Playing = false v.mu.Unlock() - return fmt.Errorf("failed to download audio: %w", err) + return err } - fmt.Printf("Download complete, starting playback\n") + logger.Log("Download complete, starting playback", types.LogOptions{ + Prefix: "Music Player", + Level: types.Info, + }) - // Check if file exists and get its size fileInfo, err := os.Stat(fileName) if err != nil { - fmt.Printf("File stat error: %v\n", err) + logger.Log("File stat error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) v.mu.Lock() v.Playing = false v.mu.Unlock() - return fmt.Errorf("file stat error: %w", err) + return err } - fmt.Printf("File size: %d bytes\n", fileInfo.Size()) - // Ensure file gets deleted after playback + logger.Log(fmt.Sprintf("File size: %d bytes", fileInfo.Size()), types.LogOptions{ + Prefix: "Music Player", + Level: types.Debug, + }) + defer os.Remove(fileName) - // Make sure we're not already in a speaking state + err = v.playAudioFile(fileName, stopChan) + if err != nil { + logger.Log("Playback error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) + } + + v.mu.Lock() + v.Playing = false + v.mu.Unlock() + + return err +} + +func (v *VoiceInstance) playAudioFile(filename string, stopChan chan bool) error { v.Connection.Speaking(false) time.Sleep(50 * time.Millisecond) - // Set speaking status - err = v.Connection.Speaking(true) + err := v.Connection.Speaking(true) if err != nil { - fmt.Printf("Speaking error: %v\n", err) - v.mu.Lock() - v.Playing = false - v.mu.Unlock() - return fmt.Errorf("speaking error: %w", err) + logger.Log("Speaking error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) + return err } defer v.Connection.Speaking(false) - // Use ffmpeg for playback - ffmpeg := exec.Command("ffmpeg", "-i", fileName, "-f", "s16le", "-ar", "48000", "-ac", "2", "pipe:1") + ffmpeg := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "quiet", "-i", filename, "-f", "s16le", "-ar", "48000", "-ac", "2", "pipe:1") ffmpegout, err := ffmpeg.StdoutPipe() if err != nil { - fmt.Printf("FFmpeg pipe error: %v\n", err) - return fmt.Errorf("ffmpeg pipe error: %w", err) + logger.Log("FFmpeg pipe error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) + return err } - ffmpeg.Stderr = os.Stderr + ffmpeg.Stderr = nil err = ffmpeg.Start() if err != nil { - fmt.Printf("FFmpeg start error: %v\n", err) - return fmt.Errorf("ffmpeg start error: %w", err) + logger.Log("FFmpeg start error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) + return err } - // Store ffmpeg process for proper cleanup ffmpegProcess := ffmpeg.Process defer func() { ffmpegProcess.Kill() - ffmpeg.Wait() // Wait for the process to exit to avoid zombies + ffmpeg.Wait() }() - // Read and send loop buf := make([]int16, frameSize*channels) playbackDone := make(chan error, 1) go func() { for { - // Read data from ffmpeg err = binary.Read(ffmpegout, binary.LittleEndian, &buf) if err == io.EOF || err == io.ErrUnexpectedEOF { playbackDone <- nil return } if err != nil { - playbackDone <- fmt.Errorf("error reading from ffmpeg: %w", err) + playbackDone <- err return } - // Encode with opus opus, err := v.OpusEncoder.Encode(buf, frameSize, maxBytes) if err != nil { - playbackDone <- fmt.Errorf("opus encoding error: %w", err) + playbackDone <- err return } - // Send to Discord select { case v.Connection.OpusSend <- opus: - // Sent successfully case <-stopChan: playbackDone <- nil return @@ -301,87 +325,25 @@ func (v *VoiceInstance) PlayYouTube(videoURL, videoID string) error { } }() - // Wait for playback to finish or stop signal select { case err := <-playbackDone: if err != nil { - fmt.Printf("Playback error: %v\n", err) + logger.Log("Playback error: "+err.Error(), types.LogOptions{ + Prefix: "Music Player", + Level: types.Error, + }) } else { - fmt.Println("Playback completed normally") + logger.Log("Playback completed", types.LogOptions{ + Prefix: "Music Player", + Level: types.Success, + }) } + return err case <-stopChan: - fmt.Println("Playback stopped by request") + logger.Log("Playback stopped by request", types.LogOptions{ + Prefix: "Music Player", + Level: types.Info, + }) + return nil } - - // Make sure to kill ffmpeg - ffmpegProcess.Kill() - - v.mu.Lock() - v.Playing = false - v.mu.Unlock() - - return nil } - -// func (v *VoiceInstance) playAudioFile(filename string, stopChan chan bool) error { -// // Start ffmpeg to convert the file to PCM -// ffmpeg := exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", "48000", "-ac", "2", "pipe:1") -// ffmpegout, err := ffmpeg.StdoutPipe() -// if err != nil { -// return fmt.Errorf("ffmpeg stdout error: %w", err) -// } - -// ffmpeg.Stderr = os.Stderr -// err = ffmpeg.Start() -// if err != nil { -// return fmt.Errorf("ffmpeg start error: %w", err) -// } - -// // Make sure to kill ffmpeg when we're done -// defer ffmpeg.Process.Kill() - -// // Set speaking status -// err = v.Connection.Speaking(true) -// if err != nil { -// return fmt.Errorf("speaking error: %w", err) -// } -// defer v.Connection.Speaking(false) - -// // Create a buffer for reading from ffmpeg -// buf := make([]int16, frameSize*channels) - -// // Read and send loop -// for { -// // Check if we've been asked to stop -// select { -// case <-stopChan: -// return nil -// default: -// // Continue playing -// } - -// // Read data from ffmpeg -// err = binary.Read(ffmpegout, binary.LittleEndian, &buf) -// if err == io.EOF || err == io.ErrUnexpectedEOF { -// // End of file -// return nil -// } -// if err != nil { -// return fmt.Errorf("error reading from ffmpeg: %w", err) -// } - -// // Encode with opus -// opus, err := v.OpusEncoder.Encode(buf, frameSize, maxBytes) -// if err != nil { -// return fmt.Errorf("opus encoding error: %w", err) -// } - -// // Send to Discord -// select { -// case v.Connection.OpusSend <- opus: -// // Sent successfully -// case <-stopChan: -// return nil -// } -// } -// } |
