package http import ( "dwelling-radio/internal/radio" "dwelling-radio/internal/statistics" "dwelling-radio/pkg/oggtag" "encoding/json" "fmt" "log" "net/http" "strings" "time" ) type DJHandlers struct { listeners *radio.ListenerCounter playlist *radio.Playlist stats statistics.Statistics curSong *radio.Song listLen int64 fallbackSong string } func NewDJHandlers(l *radio.ListenerCounter, p *radio.Playlist, stats statistics.Statistics, cs *radio.Song, n int64, fS string) *DJHandlers { return &DJHandlers{listeners: l, playlist: p, stats: stats, curSong: cs, listLen: n, fallbackSong: fS} } func (dj *DJHandlers) ListenersUpdateIcecast(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { log.Println("DJHandlers.ListenersUpdateIcecast panic:", err) } }() if err := r.ParseForm(); err != nil { log.Println("DJHandlers.ListenersUpdateIcecast:", err) w.WriteHeader(http.StatusBadRequest) return } switch r.FormValue("action") { case "listener_add": dj.listeners.Lock() _ = dj.listeners.Inc() dj.listeners.Unlock() case "listener_remove": dj.listeners.Lock() defer dj.listeners.Unlock() if _, err := dj.listeners.Dec(); err != nil { log.Println("DJHandlers.ListenersUpdateIcecast:", err) w.WriteHeader(http.StatusBadRequest) return } default: w.WriteHeader(http.StatusNotAcceptable) return } w.Header().Add("Content-Type", "text/plain") w.Header().Add("Icecast-Auth-User", "1") w.WriteHeader(http.StatusOK) } const defaultArtistTag = "[LOL, no artist tag]" const defaultTitleTag = "[No title tag for you -_-]" const defaultTitleTagNoArtist = "[And no title tag either! Pffft]" func (dj *DJHandlers) PlaylistNext(w http.ResponseWriter, _ *http.Request) { w.Header().Add("Content-Type", "text/plain") dj.playlist.Lock() nxt := dj.playlist.Next() dj.playlist.Unlock() if nxt == "" { log.Println("the end of a playlist has been reached") if nxt = dj.fallbackSong; nxt == "" { log.Println("a fallback song is not set") w.WriteHeader(http.StatusNotFound) return } } oggf, err := oggtag.NewOggFile(nxt) if err != nil { log.Println("cannot read an OGG file", nxt, ":", err) w.WriteHeader(http.StatusInternalServerError) return } newSong := radio.Song{ Artist: oggf.GetTag("artist"), Title: oggf.GetTag("title"), Duration: oggf.GetDuration(), // Here 5 seconds are being added because it is approximately the // time between the creation of this Song object and when ezstream // actually starts to play it. StartAt: time.Now()} // .Add(5 * time.Second) if newSong.Artist == "" && newSong.Title == "" { log.Println("Playlist:", nxt, "has no artist and title tags.") newSong.Artist = defaultArtistTag newSong.Title = defaultTitleTagNoArtist } else if newSong.Artist == "" { log.Println("Playlist:", nxt, "has no artist tag.") newSong.Artist = defaultArtistTag } else if newSong.Title == "" { log.Println("Playlist:", nxt, "has no title tag.") newSong.Title = defaultTitleTag } if strings.HasSuffix(nxt, "/fallback.ogg") { newSong.Artist = "Nothing to play. Playing a fallback: " + newSong.Artist } dj.listeners.Lock() dj.curSong.Listeners, dj.curSong.PeakListeners = dj.listeners.Reset() dj.listeners.Unlock() if dj.curSong.Artist != "" && !strings.Contains(dj.curSong.Artist, "no artist tag") && !strings.Contains(dj.curSong.Artist, "No title tag") { if err := dj.stats.Add(dj.curSong); err != nil { log.Println("cannot add a song to a stats DB:", err) } } *dj.curSong = newSong dj.curSong.Listeners = 0 dj.curSong.PeakListeners = 0 fmt.Fprintln(w, nxt) } func (dj *DJHandlers) Status(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "application/json") lst, err := dj.stats.LastNSongs(dj.listLen) if err != nil { log.Println("failed to fetch last n songs:", err) } dj.listeners.RLock() defer dj.listeners.RUnlock() err = json.NewEncoder(w).Encode(&struct { Current *radio.Song `json:"current_song,omitempty"` Listeners int64 `json:"listeners"` List []radio.Song `json:"last_songs,omitempty"` }{ Current: dj.curSong, Listeners: dj.listeners.Current(), List: lst}) if err != nil { log.Println("DJHandlers.Status:", err) http.Error(w, "status parsing failed", http.StatusInternalServerError) } }