diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2022-07-21 21:14:14 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2022-07-21 21:14:14 +0200 |
| commit | 8b4335c18752ce96e452dc232e57695c2fdf6a6d (patch) | |
| tree | e191e079b607126a890a0bf1498a3f8167a8204e | |
| parent | a14ffdfc999abd76c2d9937e778c932a457be08f (diff) | |
| download | WRMS-8b4335c18752ce96e452dc232e57695c2fdf6a6d.tar.gz WRMS-8b4335c18752ce96e452dc232e57695c2fdf6a6d.zip | |
intermediate commit
* add dummy backend
* enable spotify searching
* JS: use let instead of var
* improve logging
* support adding new songs from search
* rename Song.Id to Song.Uri
| -rw-r--r-- | dummyBackend.go | 29 | ||||
| -rw-r--r-- | main.go | 31 | ||||
| -rw-r--r-- | player.go | 2 | ||||
| -rw-r--r-- | spotify.go | 24 | ||||
| -rw-r--r-- | web/client.html | 82 | ||||
| -rw-r--r-- | wrms.go | 45 |
6 files changed, 169 insertions, 44 deletions
diff --git a/dummyBackend.go b/dummyBackend.go new file mode 100644 index 0000000..f2eae57 --- /dev/null +++ b/dummyBackend.go @@ -0,0 +1,29 @@ +package main + +import ( + "crypto/sha1" + "encoding/base64" +) + +type DummyBackend struct{} + +func (dummy *DummyBackend) Play(song Song) { +} + +func (dummy *DummyBackend) Pause() { +} + +func (dummy *DummyBackend) Search(keyword string) []Song { + + s := NewDummySong("Dummy Mc Crashtest", "exactly "+keyword) + return []Song{s} +} + +func NewDummySong(title string, artist string) Song { + s := Song{title, artist, "dummy", "", 0, 0, map[string]struct{}{}, map[string]struct{}{}} + h := sha1.New() + h.Write([]byte(title)) + h.Write([]byte(artist)) + s.Uri = base64.URLEncoding.EncodeToString(h.Sum(nil)) + return s +} @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "html/template" + "io/ioutil" "log" "net/http" @@ -40,6 +41,8 @@ func searchHandler(w http.ResponseWriter, r *http.Request) { log.Fatal(err) return } + + log.Println("Search returned", string(data)) w.Write(data) } @@ -51,9 +54,9 @@ func genericVoteHandler(w http.ResponseWriter, r *http.Request, vote string) { } connId := uuidCookie.Value - songId := r.URL.Query().Get("song") - log.Println("%s song %s via url %s", vote, songId, r.URL) - wrms.AdjustSongWeight(connId, songId, vote) + songUri := r.URL.Query().Get("song") + log.Println("%s song %s via url %s", vote, songUri, r.URL) + wrms.AdjustSongWeight(connId, songUri, vote) } func upHandler(w http.ResponseWriter, r *http.Request) { @@ -69,7 +72,23 @@ func unvoteHandler(w http.ResponseWriter, r *http.Request) { } func addHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Added") + data, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println("Failed to read request body", string(data)) + http.Error(w, "Failed to read request body", http.StatusInternalServerError) + return + } + + song, err := NewSongFromJson(data) + if err != nil { + http.Error(w, "Could not parse song", http.StatusBadRequest) + return + } + + log.Println(fmt.Sprintf("Added song %s", string(data))) + wrms.AddSong(song) + wrms.Broadcast(Event{"add", []Song{song}}) + fmt.Fprintf(w, "Added song %s", string(data)) } func playPauseHandler(w http.ResponseWriter, r *http.Request) { @@ -163,8 +182,8 @@ func setupRoutes() { func main() { setupRoutes() - wrms.AddSong(NewSong("Lala", "SNFMT", "local")) - wrms.AddSong(NewSong("Hobelbank", "MC Wankwichtel", "local")) + wrms.AddSong(NewDummySong("Lala", "SNFMT")) + wrms.AddSong(NewDummySong("Hobelbank", "MC Wankwichtel")) log.Println(wrms) defer wrms.Close() @@ -24,6 +24,8 @@ func NewPlayer() Player { } available_backends["spotify"] = spotify + available_backends["dummy"] = &DummyBackend{} + return Player{available_backends} } @@ -9,6 +9,7 @@ package main import ( "errors" + "fmt" "os" // "github.com/librespot-org/librespot-golang/Spotify" @@ -64,6 +65,29 @@ func (spotify *SpotifyBackend) Pause() { } func (spotify *SpotifyBackend) Search(keyword string) []Song { + session := spotify.session + resp, err := session.Mercury().Search(keyword, 12, session.Country(), session.Username()) + + if err != nil { + fmt.Println("Failed to search:", err) + return []Song{} + } + + res := resp.Results + + fmt.Println("Search results for ", keyword) + fmt.Println("=============================") + + if res.Error != nil { + fmt.Println("Search result error:", res.Error) + } + + fmt.Printf("\nTracks: %d (total %d)\n", len(res.Tracks.Hits), res.Tracks.Total) + + for _, track := range res.Tracks.Hits { + fmt.Printf(" => %v (%s)\n", track, track.Uri) + } + return []Song{} } diff --git a/web/client.html b/web/client.html index 8e3faab..38e1652 100644 --- a/web/client.html +++ b/web/client.html @@ -45,17 +45,27 @@ </script> <script> - var HttpClient = function() { - this.get = function(url, callback) { - var request = new XMLHttpRequest(); + let HttpClient = function() { + function newRequest(callback) { + let request = new XMLHttpRequest(); request.onreadystatechange = function() { if (request.readyState == 4 && request.status == 200) callback(request.responseText); } + return request; + } + this.get = function(url, callback) { + let request = newRequest(callback); request.open("GET", url, true); request.send(null); } + + this.post = function(url, data, callback) { + let request = newRequest(callback); + request.open("POST", url, true); + request.send(data); + } } let songs = []; @@ -69,18 +79,18 @@ songs.sort(function(a, b) {return b.weight - a.weight}); for (const song of songs) { - var listItem = document.createElement("li"); + let listItem = document.createElement("li"); const btnHTML = "<svg width='36' height='36'><path d='M2 10h32L18 26 2 10z' fill='currentColor'></path></svg>"; - var upvoteBtn = document.createElement("span"); - var downvoteBtn = document.createElement("span"); + let upvoteBtn = document.createElement("span"); + let downvoteBtn = document.createElement("span"); upvoteBtn.innerHTML = downvoteBtn.innerHTML = btnHTML; upvoteBtn.className = downvoteBtn.className = "vote"; upvoteBtn.style.transform = 'rotate(180deg)'; - if (votes.has(song.id)) { - if (votes.get(song.id) == "up") + if (votes.has(song.uri)) { + if (votes.get(song.uri) == "up") upvoteBtn.classList.toggle("on"); else downvoteBtn.classList.toggle("on"); @@ -91,21 +101,21 @@ btn.classList.toggle("on"); const isUpvoteBtn = btn == upvoteBtn; const isVote = btn.classList.contains("on"); - var url = ""; + let url = ""; if (isVote) { if (isUpvoteBtn) { downvoteBtn.classList.remove("on") - votes.set(song.id, "up") + votes.set(song.uri, "up") } else { upvoteBtn.classList.remove("on") - votes.set(song.id, "down") + votes.set(song.uri, "down") } - url = "/" + (isUpvoteBtn ? "up" : "down") + "?song=" + song.id; + url = "/" + (isUpvoteBtn ? "up" : "down") + "?song=" + song.uri; } else { - votes.delete(song.id); - url = "/unvote?song=" + song.id; + votes.delete(song.uri); + url = "/unvote?song=" + song.uri; } console.log("Get " + url); @@ -130,7 +140,7 @@ function handleUpdate(updated) { for (const updated_song of updated) { - songs[songs.findIndex(function(s) {return s.id == updated_song.id})] = updated_song; + songs[songs.findIndex(function(s) {return s.uri == updated_song.uri})] = updated_song; } renderPlaylist(); } @@ -143,7 +153,7 @@ document.getElementById("ppbutton").innerHTML = 'Pause'; idx = -1; - songs.forEach(function(s, i, a) { if (s.id == currentSong.id) idx = i; }); + songs.forEach(function(s, i, a) { if (s.uri == currentSong.uri) idx = i; }); if (idx != -1) { songs.splice(idx, 1); renderPlaylist(); @@ -193,13 +203,44 @@ function submitSearch() { const form = new FormData(document.getElementById("searchForm")); const pattern = new URLSearchParams(form).toString(); - new HttpClient().get("/search?" + pattern, console.log); + new HttpClient().get("/search?" + pattern, showSearchResults); return false; } + function addSong(song) { + new HttpClient().post("/add", JSON.stringify(song), console.log); + } + + function showSearchResults(responseText) { + response = JSON.parse(responseText) + if (!response) + return + + let searchResultsOverlay = document.getElementById("searchResultsOverlay"); + + let searchResults = document.getElementById("searchResults"); + // Clear search Results + searchResults.innerHTML = ""; + for (const song of response.songs) { + let listItem = document.createElement("li"); + listItem.addEventListener("click", function() { + addSong(song); + searchResultsOverlay.style.display = "none"; + }); + + let searchResultLabel = document.createTextNode(song.artist + " - " + song.title); + listItem.appendChild(searchResultLabel); + + searchResults.appendChild(listItem); + } + + // Show search result overlay + searchResultsOverlay.style.display = "block"; + } + window.onload = function() { document.getElementById("ppbutton").addEventListener("click", function() { - new HttpClient().get("/playpause", console.log); + new HttpClient().get("/playpause", showSearchResults); }); } </script> @@ -214,6 +255,11 @@ </form> </div> + <div id="searchResultsOverlay" style="display: none;"> + <ul id="searchResults"> + </ul> + </div> + <h2>Playing</h2> <p id='playing'></p> <div id='controls'> @@ -1,8 +1,7 @@ package main import ( - "crypto/sha1" - "encoding/base64" + "encoding/json" "fmt" "log" @@ -15,21 +14,24 @@ type Song struct { Title string `json:"title"` Artist string `json:"artist"` Source string `json:"source"` - Id string `json:"id"` + Uri string `json:"uri"` Weight int `json:"weight"` index int `json:"-"` // used by heap.Interface Upvotes map[string]struct{} `json:"-"` Downvotes map[string]struct{} `json:"-"` } -func NewSong(title string, artist string, source string) Song { - s := Song{title, artist, source, "", 0, 0, map[string]struct{}{}, map[string]struct{}{}} - h := sha1.New() - h.Write([]byte(title)) - h.Write([]byte(artist)) - h.Write([]byte(source)) - s.Id = base64.URLEncoding.EncodeToString(h.Sum(nil)) - return s +func NewSongFromJson(data []byte) (Song, error) { + var s Song + err := json.Unmarshal(data, &s) + if err != nil { + log.Println(fmt.Sprintf("Failed to parse song data %s with %s", string(data), err)) + return s, err + } + + s.Upvotes = map[string]struct{}{} + s.Downvotes = map[string]struct{}{} + return s, nil } type Event struct { @@ -79,15 +81,17 @@ func (wrms *Wrms) Broadcast(cmd Event) { func (wrms *Wrms) AddSong(song Song) { wrms.Songs = append(wrms.Songs, song) - wrms.Queue.Add(&wrms.Songs[len(wrms.Songs) - 1]) + s := &wrms.Songs[len(wrms.Songs)-1] + wrms.Queue.Add(s) + log.Println(fmt.Sprintf("Added song %s (ptr=%p) to Songs", s.Uri, s)) } func (wrms *Wrms) Next() { wrms.CurrentSong = wrms.Queue.PopSong() for i, s := range wrms.Songs { - if s.Id == wrms.CurrentSong.Id { - wrms.Songs[i] = wrms.Songs[len(wrms.Songs) - 1] - wrms.Songs = wrms.Songs[:len(wrms.Songs) - 1] + if s.Uri == wrms.CurrentSong.Uri { + wrms.Songs[i] = wrms.Songs[len(wrms.Songs)-1] + wrms.Songs = wrms.Songs[:len(wrms.Songs)-1] break } } @@ -109,17 +113,18 @@ func (wrms *Wrms) PlayPause() { wrms.Broadcast(ev) } -func (wrms *Wrms) AdjustSongWeight(connId string, songId string, vote string) { +func (wrms *Wrms) AdjustSongWeight(connId string, songUri string, vote string) { for i := 0; i < len(wrms.Songs); i++ { s := &wrms.Songs[i] - if s.Id != songId { + if s.Uri != songUri { continue } + log.Println(fmt.Sprintf("Adjusting song %s (ptr=%p)", s.Uri, s)) switch vote { case "up": if _, ok := s.Upvotes[connId]; ok { - log.Println(fmt.Sprintf("Double upvote of song %s by connections %s", songId, connId)) + log.Println(fmt.Sprintf("Double upvote of song %s by connections %s", songUri, connId)) return } @@ -134,7 +139,7 @@ func (wrms *Wrms) AdjustSongWeight(connId string, songId string, vote string) { case "down": if _, ok := s.Downvotes[connId]; ok { - log.Println(fmt.Sprintf("Double downvote of song %s by connections %s", songId, connId)) + log.Println(fmt.Sprintf("Double downvote of song %s by connections %s", songUri, connId)) return } @@ -157,7 +162,7 @@ func (wrms *Wrms) AdjustSongWeight(connId string, songId string, vote string) { delete(s.Upvotes, connId) s.Weight -= 1 } else { - log.Println(fmt.Sprintf("Double downvote of song %s by connections %s", songId, connId)) + log.Println(fmt.Sprintf("Double downvote of song %s by connections %s", songUri, connId)) return } |
