From 9a44380d45ace45595ee8115abf72c6a7c332e00 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Wed, 15 Oct 2025 00:13:26 -0600 Subject: [PATCH] go --- cmd/tts-room/main.go | 9 +++ go.mod | 9 ++- go.sum | 6 ++ src/public/index.html | 158 ++++++++++++++++++++++++++++++++++++++++++ src/server/message.go | 9 +++ src/server/server.go | 60 ++++++++++++++++ src/server/session.go | 106 ++++++++++++++++++++++++++++ 7 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 go.sum create mode 100644 src/public/index.html create mode 100644 src/server/message.go create mode 100644 src/server/server.go create mode 100644 src/server/session.go diff --git a/cmd/tts-room/main.go b/cmd/tts-room/main.go index da29a2c..9454b3f 100644 --- a/cmd/tts-room/main.go +++ b/cmd/tts-room/main.go @@ -1,4 +1,13 @@ package main +import ( + "net/http" + "tts-room/src/server" +) + func main() { + s := server.NewServer() + if err := http.ListenAndServe(":10000", s); err != nil { + panic(err) + } } diff --git a/go.mod b/go.mod index 69b15af..02c5cda 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module tts-room -go 1.22.2 +go 1.24.0 + +require ( + github.com/gorilla/websocket v1.5.3 + golang.org/x/time v0.14.0 +) + +require github.com/google/uuid v1.6.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4290850 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= diff --git a/src/public/index.html b/src/public/index.html new file mode 100644 index 0000000..566d0d9 --- /dev/null +++ b/src/public/index.html @@ -0,0 +1,158 @@ + + + + + + + + + + + +
+
+
+

+

+ +

+
+
+ + +
+ + +
+ + +
+
+
+
+
+ + diff --git a/src/server/message.go b/src/server/message.go new file mode 100644 index 0000000..0b4c2e9 --- /dev/null +++ b/src/server/message.go @@ -0,0 +1,9 @@ +package server + +type message struct { + Text string + Pitch int + Rate float64 + VoiceIdx int + room string +} diff --git a/src/server/server.go b/src/server/server.go new file mode 100644 index 0000000..1461587 --- /dev/null +++ b/src/server/server.go @@ -0,0 +1,60 @@ +package server + +import ( + "log" + "net/http" +) + +type Server struct { + sessions []*session +} + +func NewServer() *Server { + return &Server{} +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/ws": + if err := s.WS(w, r); err != nil { + log.Println("[ws]", err) + } + default: + http.FileServer(http.Dir("./src/public")).ServeHTTP(w, r) + } +} + +func (s *Server) WS(w http.ResponseWriter, r *http.Request) error { + sess, err := newSession(w, r, nil) + if err != nil { + return err + } + defer sess.Close() + + log.Println("someone has joined", sess.room) + defer log.Println("someone has left", sess.room) + + sess.cb = func(m message) error { + for i := range s.sessions { + if s.sessions[i].id != sess.id && s.sessions[i].room == sess.room { + select { + case s.sessions[i].scatterc <- m: + case <-s.sessions[i].ctx.Done(): + } + } + } + return nil + } + + s.sessions = append(s.sessions, sess) + defer func() { + for i := range s.sessions { + if s.sessions[i].id == sess.id { + s.sessions = append(s.sessions[:i], s.sessions[i+1:]...) + return + } + } + }() + + return sess.Run() +} diff --git a/src/server/session.go b/src/server/session.go new file mode 100644 index 0000000..a451562 --- /dev/null +++ b/src/server/session.go @@ -0,0 +1,106 @@ +package server + +import ( + "context" + "encoding/json" + "log" + "net/http" + "sync" + + "github.com/google/uuid" + "github.com/gorilla/websocket" + "golang.org/x/time/rate" +) + +type session struct { + ctx context.Context + can context.CancelFunc + ws *websocket.Conn + wg sync.WaitGroup + cb func(message) error + id string + scatterc chan (message) + room string +} + +var upgrader = websocket.Upgrader{} + +func newSession(w http.ResponseWriter, r *http.Request, cb func(message) error) (*session, error) { + c, err := upgrader.Upgrade(w, r, nil) + ctx, can := context.WithCancel(r.Context()) + return &session{ + ctx: ctx, + can: can, + ws: c, + cb: cb, + id: uuid.New().String(), + scatterc: make(chan message, 20), + room: r.URL.Query().Get("room"), + }, err +} + +func (s *session) Close() { + if s.ws != nil { + s.ws.Close() + } + s.ws = nil + s.can() + s.wg.Wait() +} + +func (s *session) Run() error { + defer s.Close() + + go s.gather() + go s.scatter() + + <-s.ctx.Done() + return s.ctx.Err() +} + +func (s *session) gather() { + s.while(func() error { + mt, msg, err := s.ws.ReadMessage() + if err != nil { + return err + } + if mt != 1 { + return nil + } + + var m message + if err := json.Unmarshal(msg, &m); err != nil { + return err + } + m.room = s.room + + return s.cb(m) + }) +} + +func (s *session) scatter() { + s.while(func() error { + select { + case m := <-s.scatterc: + b, _ := json.Marshal(m) + return s.ws.WriteMessage(1, b) + case <-s.ctx.Done(): + return s.ctx.Err() + } + }) +} + +func (s *session) while(foo func() error) { + defer s.can() + + s.wg.Add(1) + defer s.wg.Done() + + l := rate.NewLimiter(20, 1) + for l.Wait(s.ctx) == nil { + if err := foo(); err != nil { + log.Println(err) + return + } + } +}