package main import ( "context" "dndmusicbot/ffmpeg" discordspeaker "dndmusicbot/speaker" "encoding/json" "fmt" "log" "net/url" "os" "time" "github.com/faiface/beep/mp3" "github.com/google/uuid" "github.com/kataras/go-events" "golang.org/x/time/rate" ) type SongInfo struct { Playlist uuid.UUID `json:"playlist"` PlaylistName string `json:"playlistname"` Title string `json:"current"` Channel string `json:"channel"` Position int `json:"position"` Length int `json:"len"` Pause bool `json:"pause"` Song string `json:"song"` } var l = rate.Sometimes{Interval: 1 * time.Second} func init() { log.Println("events.go loading...") app.events = events.New() app.events.On("load_playlist", app.loadPlaylist) app.events.On("add_playlist", app.addPlaylist) app.events.On("preload_song", app.preloadSong) app.events.On("song_over", app.songOver) //app.events.On("song_position", app.songPosition) app.events.On("ambiance_play", app.ambiancePlay) app.events.On("ambiance_stop", app.ambianceStop) app.events.On("stop", app.stop) app.events.On("next", app.nextSong) app.events.On("prev", app.prevSong) //app.events.On("tick", app.checkQueue) app.events.On("tick", app.songPosition) app.events.On("tick", app.checkTimeleft) } func (app *App) songInfoEvent(event string) map[string]interface{} { msg := make(map[string]interface{}) msg["event"] = event var title, channel string switch current := app.queue.Current().(type) { case *ffmpeg.PCM: title = current.Player.Title channel = current.Player.Channel } var plid uuid.UUID var pltitle string if app.playlist != nil { plid = app.playlist.Id pltitle = app.playlist.Title } var song string if app.active != nil && len(app.active) > 0 { song = app.active[app.plidx] } msg["payload"] = SongInfo{ Playlist: plid, PlaylistName: pltitle, Title: title, Channel: channel, Position: app.queue.Position(), Length: app.queue.Len(), Pause: !app.queue.IsPlaying(), Song: song, } return msg } func (app *App) ambiancePlay(payload ...interface{}) { if !(len(payload) > 0) { log.Println("ambiance_play called without a payload.") return } var fn string switch data := payload[0].(type) { case json.RawMessage: var err error err = json.Unmarshal(data, &fn) if err != nil { log.Println(err) return } default: log.Println("loadPlaylist called with invalid payload.") return } f, err := os.Open(fmt.Sprintf("./ambiance/%s.mp3", fn)) if err != nil { log.Fatal(err) } play, _, err := mp3.Decode(f) if err != nil { log.Fatal(err) } discordspeaker.Pause(false) discordspeaker.Lock() if app.ambiance.IsPlaying() { app.ambiance.Reset() } app.ambiance.Add(play) discordspeaker.Unlock() msg := make(map[string]interface{}) out := make(map[string]interface{}) app.curamb = fn msg["event"] = "ambiance_play" out["type"] = fn msg["payload"] = out ws_msg <- msg } func (app *App) ambianceStop(payload ...interface{}) { log.Println("Stopping ambiance") discordspeaker.Lock() app.ambiance.Reset() discordspeaker.Unlock() msg := make(map[string]interface{}) msg["event"] = "ambiance_stop" ws_msg <- msg } func (app *App) songPosition(payload ...interface{}) { if !app.queue.IsPlaying() { return } l.Do(func() { msg := make(map[string]interface{}) out := make(map[string]interface{}) msg["event"] = "song_position" if app.queue != nil { out["len"] = app.queue.Len() out["position"] = app.queue.Position() } msg["payload"] = out ws_msg <- msg }) } func (app *App) checkQueue(payload ...interface{}) { if !app.queue.IsPlaying() { return } // This needs some tweaking. if app.queue.playing && !app.next && app.queue.QLen() == 0 { log.Println("Queue is 0. It should never be 0..") ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) defer cancel() select { case <-ctx.Done(): if app.queue.QLen() == 0 { log.Println("Queue is still 0. Queueing a song.") app.events.Emit("next") } log.Println("Seems queue is filled, doing nothing.") } } } func (app *App) checkTimeleft(payload ...interface{}) { if !app.queue.IsPlaying() { return } timeleft := app.queue.Len() - app.queue.Position() if timeleft <= 10000 && timeleft > 0 && !app.next { app.events.Emit("preload_song") } } func (app *App) songOver(payload ...interface{}) { log.Println("song_over event received") msg := app.songInfoEvent("song_info") ws_msg <- msg app.next = false } func (app *App) stop(payload ...interface{}) { log.Println("stop event received") discordspeaker.Lock() app.queue.Reset() discordspeaker.Unlock() msg := make(map[string]interface{}) msg["event"] = "stop" ws_msg <- msg } func (app *App) prevSong(payload ...interface{}) { log.Println("prev_song event received") song := app.GetPrevSong(app.active) f, err := ffmpeg.NewPCM(song, sampleRate, channels) if err != nil { log.Println("Unable to start new ffmpeg") return } discordspeaker.Lock() app.queue.Reset() app.queue.Add(f) discordspeaker.Unlock() msg := app.songInfoEvent("song_info") ws_msg <- msg } func (app *App) nextSong(payload ...interface{}) { log.Println("next_song event received") song := app.GetNextSong(app.active) f, err := ffmpeg.NewPCM(song, sampleRate, channels) if err != nil { log.Println("Unable to start new ffmpeg") return } for { time.Sleep(1 * time.Second) if app.queue.QLen() == 0 { log.Println("retrying...") song = app.GetSong(app.active) } else { break } } discordspeaker.Lock() app.queue.Reset() app.queue.Add(f) discordspeaker.Unlock() msg := app.songInfoEvent("song_info") ws_msg <- msg } func (app *App) addPlaylist(payload ...interface{}) { if !(len(payload) > 0) { log.Println("addPlaylist called without a payload.") return } log.Println("add_playlist event received") var data map[string]string switch js := payload[0].(type) { case json.RawMessage: json.Unmarshal(js, &data) default: log.Println("newPlaylist called with invalid payload.") return } plurl, ok := data["url"] if !ok { log.Println("addPlaylist without url") return } pltitle, ok := data["title"] if !ok { log.Println("addPlaylist without title") return } pl, err := url.Parse(plurl) if err != nil { log.Println("addPlaylist invalid url") return } plid := pl.Query().Get("list") if plid == "" { log.Println("addPlaylist missing list in url") return } _, err = app.Playlist(plid) if err != nil { log.Println("Error getting youtube playlist info,", plid) } id, err := app.AddPlaylist(pltitle, plid) if err != nil { log.Println("Error getting youtube playlist info,", plid) } msg := make(map[string]interface{}) msg["event"] = "new_playlist" msg["payload"] = map[string]string{"url": id.String(), "title": pltitle} ws_msg <- msg } func (app *App) loadPlaylist(payload ...interface{}) { log.Println("load_playlist event received") if !(len(payload) > 0) { log.Println("loadPlaylist called without a payload.") return } var id uuid.UUID switch data := payload[0].(type) { case json.RawMessage: var err error var tmp string json.Unmarshal(data, &tmp) id, err = uuid.Parse(tmp) if err != nil { log.Println("Unable to parse UUID,", err) } case uuid.UUID: id = data default: log.Println("loadPlaylist called with invalid payload.") return } log.Println("Loading new playlist: ", id) pl, err := app.GetPlaylist(id) if err != nil { log.Println("Unable to find playlist with id,", id) return } app.plm.Lock() app.playlist = pl app.plm.Unlock() app.next = true discordspeaker.Pause(false) list, err := app.Playlist(pl.Url) if err != nil { log.Println("Error getting playlist info,", id) return } list, err = ShufflePlaylist(list) if err != nil { log.Println("Unable to shuffle playlist") return } app.active = list song := app.GetNextSong(list) f, err := ffmpeg.NewPCM(song, sampleRate, channels) if err != nil { log.Println("Unable to start new ffmpeg") return } discordspeaker.Lock() app.queue.Reset() app.queue.Add(f) discordspeaker.Unlock() app.next = false msg := app.songInfoEvent("song_info") ws_msg <- msg log.Println("Added song", song) } func (app *App) preloadSong(payload ...interface{}) { log.Println("preload_song event received") app.next = true discordspeaker.Pause(false) var song string switch current := app.queue.Current().(type) { case *ffmpeg.PCM: for { song = app.GetNextSong(app.active) if current.Uri != song { break } log.Println("Got same song, try again!") } default: song = app.GetNextSong(app.active) } f, err := ffmpeg.NewPCM(song, sampleRate, channels) if err != nil { log.Fatal(err) } discordspeaker.Lock() app.queue.Add(f) discordspeaker.Unlock() log.Println("Added song.", song) }