選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

164 行
4.0KB

  1. package main
  2. import (
  3. "encoding/json"
  4. "log"
  5. "net/http"
  6. "strconv"
  7. "time"
  8. "github.com/gorilla/websocket"
  9. "sdr-visual-suite/internal/recorder"
  10. )
  11. func registerWSHandlers(mux *http.ServeMux, h *hub, recMgr *recorder.Manager) {
  12. upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
  13. origin := r.Header.Get("Origin")
  14. if origin == "" || origin == "null" {
  15. return true
  16. }
  17. return true
  18. }}
  19. mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
  20. conn, err := upgrader.Upgrade(w, r, nil)
  21. if err != nil {
  22. log.Printf("ws upgrade failed: %v (origin: %s)", err, r.Header.Get("Origin"))
  23. return
  24. }
  25. c := &client{conn: conn, send: make(chan []byte, 32), done: make(chan struct{})}
  26. h.add(c)
  27. defer func() {
  28. h.remove(c)
  29. _ = conn.Close()
  30. }()
  31. conn.SetReadDeadline(time.Now().Add(60 * time.Second))
  32. conn.SetPongHandler(func(string) error {
  33. conn.SetReadDeadline(time.Now().Add(60 * time.Second))
  34. return nil
  35. })
  36. go func() {
  37. ping := time.NewTicker(30 * time.Second)
  38. defer ping.Stop()
  39. for {
  40. select {
  41. case msg, ok := <-c.send:
  42. if !ok {
  43. return
  44. }
  45. _ = conn.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))
  46. if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
  47. return
  48. }
  49. case <-ping.C:
  50. _ = conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
  51. if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
  52. log.Printf("ws ping error: %v", err)
  53. return
  54. }
  55. }
  56. }
  57. }()
  58. for {
  59. _, _, err := conn.ReadMessage()
  60. if err != nil {
  61. return
  62. }
  63. }
  64. })
  65. // /ws/audio — WebSocket endpoint for continuous live-listen audio streaming.
  66. // Client connects with query params: freq, bw, mode
  67. // Server sends binary frames of PCM s16le audio at 48kHz.
  68. mux.HandleFunc("/ws/audio", func(w http.ResponseWriter, r *http.Request) {
  69. q := r.URL.Query()
  70. freq, _ := strconv.ParseFloat(q.Get("freq"), 64)
  71. bw, _ := strconv.ParseFloat(q.Get("bw"), 64)
  72. mode := q.Get("mode")
  73. if freq <= 0 {
  74. http.Error(w, "freq required", http.StatusBadRequest)
  75. return
  76. }
  77. if bw <= 0 {
  78. bw = 12000
  79. }
  80. streamer := recMgr.StreamerRef()
  81. if streamer == nil {
  82. http.Error(w, "streamer not available", http.StatusServiceUnavailable)
  83. return
  84. }
  85. subID, ch := streamer.SubscribeAudio(freq, bw, mode)
  86. if ch == nil {
  87. http.Error(w, "no active stream for this frequency", http.StatusNotFound)
  88. return
  89. }
  90. conn, err := upgrader.Upgrade(w, r, nil)
  91. if err != nil {
  92. streamer.UnsubscribeAudio(subID)
  93. log.Printf("ws/audio upgrade failed: %v", err)
  94. return
  95. }
  96. defer func() {
  97. streamer.UnsubscribeAudio(subID)
  98. _ = conn.Close()
  99. }()
  100. log.Printf("ws/audio: client connected freq=%.1fMHz mode=%s", freq/1e6, mode)
  101. // Send audio stream info as first text message
  102. info := map[string]any{
  103. "type": "audio_info",
  104. "sample_rate": 48000,
  105. "channels": 1,
  106. "format": "s16le",
  107. "freq": freq,
  108. "mode": mode,
  109. }
  110. if infoBytes, err := json.Marshal(info); err == nil {
  111. _ = conn.WriteMessage(websocket.TextMessage, infoBytes)
  112. }
  113. // Read goroutine (to detect disconnect)
  114. done := make(chan struct{})
  115. go func() {
  116. defer close(done)
  117. for {
  118. _, _, err := conn.ReadMessage()
  119. if err != nil {
  120. return
  121. }
  122. }
  123. }()
  124. ping := time.NewTicker(30 * time.Second)
  125. defer ping.Stop()
  126. for {
  127. select {
  128. case pcm, ok := <-ch:
  129. if !ok {
  130. log.Printf("ws/audio: stream ended freq=%.1fMHz", freq/1e6)
  131. return
  132. }
  133. _ = conn.SetWriteDeadline(time.Now().Add(500 * time.Millisecond))
  134. if err := conn.WriteMessage(websocket.BinaryMessage, pcm); err != nil {
  135. log.Printf("ws/audio: write error: %v", err)
  136. return
  137. }
  138. case <-ping.C:
  139. _ = conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
  140. if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
  141. return
  142. }
  143. case <-done:
  144. log.Printf("ws/audio: client disconnected freq=%.1fMHz", freq/1e6)
  145. return
  146. }
  147. }
  148. })
  149. }