package main import ( "context" "dndmusicbot/ffmpeg" discordspeaker "dndmusicbot/speaker" "encoding/json" "fmt" "log" "net/url" "os" "time" "github.com/faiface/beep" "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,omitempty"` PlaylistName string `json:"playlistname,omitempty"` Title string `json:"current,omitempty"` Channel string `json:"channel,omitempty"` Position int `json:"position"` Length int `json:"len,omitempty"` Pause bool `json:"pause"` Song string `json:"song,omitempty"` } 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.songInfo) app.events.On("song_start", app.songInfo) //app.events.On("song_position", app.songPosition) app.events.On("ambiance_play", app.ambiancePlay) app.events.On("ambiance_stop", app.ambianceStop) app.events.On("ambiance_add", app.ambianceAdd) 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 msg["payload"] = SongInfo{ Playlist: app.queue.Current().Playlist.Id, PlaylistName: app.queue.Current().Playlist.Title, Title: app.queue.Current().Title, Channel: app.queue.Current().Channel, Position: 0, Length: app.queue.Len(), Pause: !app.queue.IsPlaying(), Song: app.queue.Current().VideoID, } 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() app.ambiance.Clear() loop := beep.Loop(-1, play) app.ambiance.Add(loop) 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.Clear() discordspeaker.Unlock() msg := make(map[string]interface{}) msg["event"] = "ambiance_stop" ws_msg <- msg } func (app *App) ambianceAdd(payload ...interface{}) { if !(len(payload) > 0) { log.Println("addPlaylist called without a payload.") return } log.Println("ambiance_add 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 } amburl, ok := data["url"] if !ok { log.Println("addPlaylist without url") return } ambtitle, ok := data["title"] if !ok { log.Println("addPlaylist without title") return } err := DownloadAmbiance(amburl, ambtitle) if err != nil { log.Println(err) return } msg := make(map[string]interface{}) out := make(map[string]interface{}) msg["event"] = "ambiance_add" out["type"] = ambtitle msg["payload"] = out 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" 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) songInfo(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") app.queue.Prev() msg := app.songInfoEvent("song_info") ws_msg <- msg } func (app *App) nextSong(payload ...interface{}) { log.Println("next_song event received") app.queue.Next() 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 } 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.queue.Reset() go func() { for _, vid := range list { ytinfo, err := app.Video(vid) if err != nil { log.Println(err) continue } _, yt, err := app.cache.GetOrCreateBytes(vid+".txt", func() ([]byte, error) { uri, err := NewYTdl(vid) if err != nil { return nil, err } return uri, nil }) ff, err := ffmpeg.NewPCM(string(yt), sampleRate, channels) if err != nil { log.Println(err) continue } song := &Song{ Title: ytinfo.Title, Channel: ytinfo.Channel, VideoID: vid, Length: ytinfo.Len, PCM: ff, Playlist: *pl, DLuri: string(yt), } app.queue.Add(song) } }() } func (app *App) preloadSong(payload ...interface{}) { app.queue.Preload() }