Skip to content

Commit

Permalink
feat status signals
Browse files Browse the repository at this point in the history
on play message
  • Loading branch information
keshon committed Nov 13, 2024
1 parent 93dac14 commit 95d5015
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 61 deletions.
48 changes: 36 additions & 12 deletions cmd/melodix/melodix.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (b *Bot) Start() error {

func (b *Bot) Shutdown() {
for _, instance := range b.players {
instance.Signals <- player.ActionStop
instance.ActionSignals <- player.ActionStop
}
if err := b.session.Close(); err != nil {
log.Println("Error closing connection:", err)
Expand All @@ -111,6 +111,7 @@ func (b *Bot) configureIntents() {
func (b *Bot) registerEventHandlers() {
b.session.AddHandler(b.onReady)
b.session.AddHandler(b.onMessageCreate)
b.session.AddHandler(b.onPlayback)
}

// Event Handlers
Expand Down Expand Up @@ -224,20 +225,22 @@ func (b *Bot) onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate)
case "now":
instance := b.getOrCreatePlayer(m.GuildID)
if instance.Song != nil {
title, source, url, err := instance.Song.GetInfo(instance.Song)
if err != nil {
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Error getting song info: %v", err))
emb := embed.NewEmbed().SetColor(embedColor)
emb.SetDescription(fmt.Sprintf("%s Now playing\n\n**%s**\n[%s](%s)", player.StatusPlaying.StringEmoji(), instance.Song.Title, instance.Song.Source, instance.Song.PublicLink))
if len(instance.Song.Thumbnail.URL) > 0 {
emb.SetThumbnail(instance.Song.Thumbnail.URL)
}
s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("Now playing from %s:\n[%s](%s)", source, title, url))
emb.SetFooter(fmt.Sprintf("Use %shelp for a list of commands.", b.prefixCache[m.GuildID]))
s.ChannelMessageSendEmbed(m.ChannelID, emb.MessageEmbed)
return
}
s.ChannelMessageSend(m.ChannelID, "No song is currently playing.")
case "stop":
instance := b.getOrCreatePlayer(m.GuildID)
instance.Signals <- player.ActionStop
instance.ActionSignals <- player.ActionStop
case "skip":
instance := b.getOrCreatePlayer(m.GuildID)
instance.Signals <- player.ActionSkip
instance.ActionSignals <- player.ActionSkip
case "pause", "resume":
instance := b.getOrCreatePlayer(m.GuildID)
voiceState, err := b.findUserVoiceState(m.GuildID, m.Author.ID)
Expand All @@ -248,9 +251,9 @@ func (b *Bot) onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate)
}
if instance.ChannelID != voiceState.ChannelID {
instance.ChannelID = voiceState.ChannelID
instance.Signals <- player.ActionSwap
instance.ActionSignals <- player.ActionSwap
} else {
instance.Signals <- player.ActionPauseResume
instance.ActionSignals <- player.ActionPauseResume
}
case "list":
instance := b.getOrCreatePlayer(m.GuildID)
Expand Down Expand Up @@ -349,9 +352,7 @@ func (b *Bot) onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate)
Inline: false,
})
}
s.ChannelMessageSendComplex(m.ChannelID, &discordgo.MessageSend{
Embeds: []*discordgo.MessageEmbed{emb},
})
s.ChannelMessageSendEmbed(m.ChannelID, emb)
case "set-prefix":
emb := embed.NewEmbed().SetColor(embedColor)
if len(param) == 0 {
Expand All @@ -368,6 +369,29 @@ func (b *Bot) onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate)
}
}

func (b *Bot) onPlayback(s *discordgo.Session, m *discordgo.MessageCreate) {
if m.Author.ID == s.State.User.ID {
return
}
go func() {
instance := b.getOrCreatePlayer(m.GuildID)
signal := <-instance.StatusSignals
if signal == player.StatusPlaying {
if instance.Song != nil {
emb := embed.NewEmbed().SetColor(embedColor)
emb.SetDescription(fmt.Sprintf("%s Now playing\n\n**%s**\n[%s](%s)", player.StatusPlaying.StringEmoji(), instance.Song.Title, instance.Song.Source, instance.Song.PublicLink))
if len(instance.Song.Thumbnail.URL) > 0 {
emb.SetThumbnail(instance.Song.Thumbnail.URL)
}
emb.SetFooter(fmt.Sprintf("Use %shelp for a list of commands.", b.prefixCache[m.GuildID]))
s.ChannelMessageSendEmbed(m.ChannelID, emb.MessageEmbed)
return
}
s.ChannelMessageSend(m.ChannelID, "No song is currently playing.")
}
}()
}

// Utility Methods
func (b *Bot) getOrCreatePlayer(guildID string) *player.Player {
if player, exists := b.players[guildID]; exists {
Expand Down
14 changes: 7 additions & 7 deletions datastore/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
)

type DataStore struct {
data map[string]interface{} // In-memory data storage
file string // File path for persistent storage
mu sync.RWMutex // Mutex for thread-safe access
ticker *time.Ticker // Ticker for periodic saving
done chan bool // Channel for shutting down the auto-save
data map[string]interface{} // in-memory data storage
file string // file path for persistent storage
mu sync.RWMutex // mutex for thread-safe access
ticker *time.Ticker // ticker for periodic saving
done chan bool // channel for shutting down the auto-save
}

func New(filePath string) (*DataStore, error) {
Expand All @@ -38,8 +38,8 @@ func New(filePath string) (*DataStore, error) {
return nil, fmt.Errorf("failed to check file existence: %v", err)
}

go store.autoSave() // Start the auto-save routine
store.handleShutdown() // Setup graceful shutdown
go store.autoSave() // start the auto-save routine
store.handleShutdown() // setup graceful shutdown

return store, nil
}
Expand Down
71 changes: 32 additions & 39 deletions player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,44 @@ import (
)

type Player struct {
ChannelID string
GuildID string
Session *discordgo.Session
Status Status
Storage *storage.Storage
Song *songpkg.Song
Queue []*songpkg.Song
Signals chan Signal
ChannelID string
GuildID string
Session *discordgo.Session
Storage *storage.Storage
Song *songpkg.Song
Queue []*songpkg.Song
StatusSignals chan StatusSignal
ActionSignals chan ActionSignal
}

func New(ds *discordgo.Session, s *storage.Storage) *Player {
return &Player{
Session: ds,
Status: StatusResting,
Storage: s,
Queue: make([]*songpkg.Song, 0, 10),
Signals: make(chan Signal, 1),
Session: ds,
Storage: s,
Queue: make([]*songpkg.Song, 0, 10),
StatusSignals: make(chan StatusSignal, 1),
ActionSignals: make(chan ActionSignal, 1),
}
}

type Status int32
type Signal int32
type StatusSignal int32
type ActionSignal int32

const (
StatusResting Status = iota
StatusPlaying
StatusPaused
StatusError

ActionStop Signal = iota // Stop the player
ActionSkip // Skip the current song
ActionSwap // Channel swap
ActionPauseResume // Pause or resume
ActionPlay // Play
StatusPlaying StatusSignal = iota
StatusPaused // reserved, not used
StatusResting // reserved, not used
StatusError // reserved, not used

ActionStop ActionSignal = iota // stop the player
ActionSkip // skip the current song
ActionSwap // channel swap
ActionPauseResume // pause or resume
ActionPlay // play
)

func (status Status) String() string {
m := map[Status]string{
func (status StatusSignal) String() string {
m := map[StatusSignal]string{
StatusResting: "Resting",
StatusPlaying: "Playing",
StatusPaused: "Paused",
Expand All @@ -65,8 +65,8 @@ func (status Status) String() string {
return m[status]
}

func (status Status) StringEmoji() string {
m := map[Status]string{
func (status StatusSignal) StringEmoji() string {
m := map[StatusSignal]string{
StatusResting: "💤",
StatusPlaying: "▶️",
StatusPaused: "⏸",
Expand Down Expand Up @@ -121,10 +121,8 @@ PLAYBACK_LOOP:
done := make(chan error)
// defer close(done)

p.Status = StatusResting
streaming := dca.NewStream(encoding, vc, done)
p.Status = StatusPlaying
p.Signals <- ActionPlay
p.StatusSignals <- StatusPlaying

if startAt == 0 {
err := p.Storage.AddTrackCountByOne(p.GuildID, p.Song.SongID, p.Song.Title, p.Song.Source.String(), p.Song.PublicLink)
Expand Down Expand Up @@ -181,31 +179,28 @@ PLAYBACK_LOOP:
encoding.Stop()
encoding.Cleanup()
p.Song = nil
p.Status = StatusResting
continue PLAYBACK_LOOP
}
// finished
fmt.Printf("Finished playback of \"%v\"", p.Song.Title)
return nil
case signal := <-p.Signals:
case signal := <-p.ActionSignals:
switch signal {
case ActionSkip:
if len(p.Queue) > 0 {
startAt = 0
encoding.Stop()
encoding.Cleanup()
p.Song = nil
p.Status = StatusResting
continue PLAYBACK_LOOP
}
p.Signals <- ActionStop
p.ActionSignals <- ActionStop
case ActionStop:
startAt = 0
encoding.Stop()
encoding.Cleanup()
p.Song = nil
p.Queue = nil
p.Status = StatusResting
return p.leaveVoiceChannel(vc)
case ActionSwap:
encoding.Stop()
Expand All @@ -214,11 +209,9 @@ PLAYBACK_LOOP:
case ActionPauseResume:
if streaming.Paused() {
streaming.SetPaused(false)
p.Status = StatusPlaying
vc.Speaking(true)
} else {
streaming.SetPaused(true)
p.Status = StatusPaused
vc.Speaking(false)
}
}
Expand Down
5 changes: 2 additions & 3 deletions song/song.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ type SongSource int32
const (
SourceYouTube SongSource = iota
SourceRadioStream
SourceLocalFile
SourceLocalFile // reserved, not used
)

func (source SongSource) String() string {
sources := map[SongSource]string{
SourceYouTube: "YouTube",
SourceRadioStream: "RadioStream",
SourceLocalFile: "LocalFile", // not implemented
SourceLocalFile: "LocalFile", // reserved, not used
}

return sources[source]
Expand All @@ -77,7 +77,6 @@ func New() *Song {
}

func (s *Song) FetchSongs(urlOrTitle string) ([]*Song, error) {

switch {
case isYoutubeURL(urlOrTitle):
if strings.Contains(urlOrTitle, "list=") {
Expand Down

0 comments on commit 95d5015

Please sign in to comment.