diff --git a/README.md b/README.md
index 33ab8c0..b3c4818 100644
--- a/README.md
+++ b/README.md
@@ -32,3 +32,8 @@ go build server.go
```
Lorsque vous lancerez le serveur avec `./server` le programme viendra chercher tous les mp3 à la racine du dossier **music** et l'UI compilée devra être dans le dossier **dist**. Vous pouvez simplement copier le dossier dist produit par `npm run build` au même endroit que l'executable `server`.
+
+# Crédits
+
+Le compteur de personnes connectées utilise un artwork de Keith Haring.
+La police d'écriture utilisée pour le titre est [Momentz](https://www.dafont.com/momentz.font) de [Khurasan Studio](https://khurasanstudio.com/).
diff --git a/index.html b/index.html
index 7151fc6..f901b86 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,7 @@
-
+
Radio menthe à l'eau 🍹
diff --git a/public/menthealeau.svg b/public/menthealeau.svg
new file mode 100644
index 0000000..a271693
--- /dev/null
+++ b/public/menthealeau.svg
@@ -0,0 +1,79 @@
+
+
diff --git a/server.go b/server.go
index cb2eba2..7358acf 100644
--- a/server.go
+++ b/server.go
@@ -37,12 +37,21 @@ type ConnectionPool struct {
}
type MetadataConnection struct {
- metadataSent bool
+ metadataSent bool
}
type MetadataConnectionPool struct {
- MetadataConnectionMap map[*MetadataConnection]struct{}
- mu sync.Mutex
+ MetadataConnectionMap map[*MetadataConnection]struct{}
+ mu sync.Mutex
+}
+
+type PeopleConnection struct {
+ numberSent int
+}
+
+type PeopleConnectionPool struct {
+ PeopleConnectionMap map[*PeopleConnection]struct{}
+ mu sync.Mutex
}
// Audio connection pool
@@ -103,6 +112,24 @@ func (cp *MetadataConnectionPool) Broadcast() {
}
}
+// People connection pool
+func (cp *PeopleConnectionPool) AddConnection(connection *PeopleConnection) {
+ defer cp.mu.Unlock()
+ cp.mu.Lock()
+ cp.PeopleConnectionMap[connection] = struct{}{}
+}
+
+func (cp *PeopleConnectionPool) DeleteConnection(connection *PeopleConnection) {
+ defer cp.mu.Unlock()
+ cp.mu.Lock()
+ delete(cp.PeopleConnectionMap, connection)
+}
+
+func NewPeopleConnectionPool() *PeopleConnectionPool {
+ peopleConnectionMap := make(map[*PeopleConnection]struct{})
+ return &PeopleConnectionPool{PeopleConnectionMap: peopleConnectionMap}
+}
+
func getFileDelay(mp3FilePath string) int64 {
t := 0.0
size := 0
@@ -136,13 +163,31 @@ func getFileDelay(mp3FilePath string) int64 {
return delayVal
}
-func streamFolder(connectionPool *ConnectionPool, metadataConnectionPool *MetadataConnectionPool, list []string) {
+func streamFolder(connectionPool *ConnectionPool, metadataConnectionPool *MetadataConnectionPool, folder string) {
buffer := make([]byte, BUFFERSIZE)
- for _, music := range list {
+ music_files := []string{}
- file, err := os.Open(filepath.Join("./music/", music))
+ entries, err := os.ReadDir(folder)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, e := range entries {
+ if filepath.Ext(e.Name()) == ".mp3" {
+ music_files = append(music_files, e.Name())
+ }
+ }
+
+ log.Printf("%d Music file found.", len(music_files))
+
+ rand.Seed(time.Now().UnixNano())
+ rand.Shuffle(len(music_files), func(i, j int) { music_files[i], music_files[j] = music_files[j], music_files[i] })
+
+ for _, music := range music_files {
+
+ file, err := os.Open(filepath.Join(folder, music))
if err != nil {
log.Fatal(err)
}
@@ -182,7 +227,7 @@ func streamFolder(connectionPool *ConnectionPool, metadataConnectionPool *Metada
// clear() is a new builtin function introduced in go 1.21. Just reinitialize the buffer if on a lower version.
clear(buffer)
tempfile := bytes.NewReader(ctn)
- delay := getFileDelay(filepath.Join("./music/", music))
+ delay := getFileDelay(filepath.Join(folder, music))
ticker := time.NewTicker(time.Millisecond * time.Duration(delay))
// Send one buffer in advance to avoid client choking
@@ -204,37 +249,24 @@ func streamFolder(connectionPool *ConnectionPool, metadataConnectionPool *Metada
}
+ streamFolder(connectionPool, metadataConnectionPool, folder)
+
}
func main() {
connPool := NewConnectionPool()
metadataConnPool := NewMetadataConnectionPool()
+ peopleConnPool := NewPeopleConnectionPool()
- music_files := []string{}
- entries, err := os.ReadDir("./music")
- if err != nil {
- log.Fatal(err)
- }
-
- for _, e := range entries {
- if filepath.Ext(e.Name()) == ".mp3" {
- music_files = append(music_files, e.Name())
- }
- }
-
- log.Printf("%d Music file found.", len(music_files))
-
- rand.Seed(time.Now().UnixNano())
- rand.Shuffle(len(music_files), func(i, j int) { music_files[i], music_files[j] = music_files[j], music_files[i] })
-
- go streamFolder(connPool, metadataConnPool, music_files)
+ go streamFolder(connPool, metadataConnPool, "./music")
http.Handle("/", http.FileServer(http.Dir("./dist")))
http.HandleFunc("/stream", func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "audio/mp3")
+ w.Header().Set("Cache-Control", "no-cache")
w.Header().Add("Connection", "keep-alive")
flusher, ok := w.(http.Flusher)
@@ -268,6 +300,13 @@ func main() {
connection := &MetadataConnection{metadataSent: false}
metadataConnPool.AddConnection(connection)
+
+ go func(done <-chan struct{}) {
+ <-done
+ metadataConnPool.DeleteConnection(connection)
+ log.Printf("%s's connection to the metadata stream has been closed\n", r.Host)
+ }(r.Context().Done())
+
log.Printf("%s has connected to the metadata stream\n", r.Host)
// Simulate sending events (you can replace this with real data)
for {
@@ -279,9 +318,13 @@ func main() {
finalData, err := json.Marshal(data)
if err != nil {
- log.Fatal(err)
+ log.Print(err)
+ }
+
+ if _, err := fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("%s", finalData)); err != nil {
+ log.Printf("Error on metadata stream : %s", err)
+ return
}
- fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("%s", finalData))
w.(http.Flusher).Flush()
connection.metadataSent = true
}
@@ -289,6 +332,40 @@ func main() {
}
})
+ http.HandleFunc("/listeners", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Access-Control-Expose-Headers", "Content-Type")
+
+ w.Header().Set("Content-Type", "text/event-stream")
+ w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Connection", "keep-alive")
+
+ connection := &PeopleConnection{numberSent: 0}
+ peopleConnPool.AddConnection(connection)
+
+ go func(done <-chan struct{}) {
+ <-done
+ peopleConnPool.DeleteConnection(connection)
+ log.Printf("%s's connection to the listeners stream has been closed\n", r.Host)
+ }(r.Context().Done())
+
+ log.Printf("%s has connected to the listeners stream\n", r.Host)
+ for {
+ if(connection.numberSent != len(connPool.ConnectionMap)) {
+ connection.numberSent = len(connPool.ConnectionMap)
+ // n, err := fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("%d", connection.numberSent))
+ _, err := w.Write([]byte(fmt.Sprintf("data: %d\n\n", connection.numberSent)))
+
+ if err != nil {
+ log.Printf("Error on listeners stream : %s", err)
+ return
+ }
+ }
+ w.(http.Flusher).Flush()
+ time.Sleep(time.Second * 1)
+ }
+ })
+
log.Println("Listening on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
diff --git a/src/App.svelte b/src/App.svelte
index 1e27a59..6b7dbfa 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -1,10 +1,18 @@