aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2022-07-21 21:14:14 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2022-07-21 21:14:14 +0200
commit8b4335c18752ce96e452dc232e57695c2fdf6a6d (patch)
treee191e079b607126a890a0bf1498a3f8167a8204e
parenta14ffdfc999abd76c2d9937e778c932a457be08f (diff)
downloadWRMS-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.go29
-rw-r--r--main.go31
-rw-r--r--player.go2
-rw-r--r--spotify.go24
-rw-r--r--web/client.html82
-rw-r--r--wrms.go45
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
+}
diff --git a/main.go b/main.go
index 9c89693..657b211 100644
--- a/main.go
+++ b/main.go
@@ -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()
diff --git a/player.go b/player.go
index 1f5967d..d46311f 100644
--- a/player.go
+++ b/player.go
@@ -24,6 +24,8 @@ func NewPlayer() Player {
}
available_backends["spotify"] = spotify
+ available_backends["dummy"] = &DummyBackend{}
+
return Player{available_backends}
}
diff --git a/spotify.go b/spotify.go
index a813254..a4d8080 100644
--- a/spotify.go
+++ b/spotify.go
@@ -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'>
diff --git a/wrms.go b/wrms.go
index b648744..f7a7395 100644
--- a/wrms.go
+++ b/wrms.go
@@ -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
}