Skip to content

Commit e2b60d6

Browse files
author
Datanoise
committed
debug: add Go Live connection tracing and backend data flow logging
1 parent 3192470 commit e2b60d6

5 files changed

Lines changed: 498 additions & 8 deletions

File tree

relay/webrtc.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/kazzmir/opus-go/ogg"
1414
"github.com/pion/webrtc/v4"
1515
"github.com/pion/webrtc/v4/pkg/media"
16+
"github.com/pion/webrtc/v4/pkg/media/oggwriter"
1617
"github.com/sirupsen/logrus"
1718
)
1819

@@ -115,6 +116,93 @@ func (wm *WebRTCManager) HandleOffer(mount string, offer webrtc.SessionDescripti
115116
return peerConnection.LocalDescription(), nil
116117
}
117118

119+
type relayWriter struct {
120+
relay *Relay
121+
stream *Stream
122+
}
123+
124+
func (rw *relayWriter) Write(p []byte) (n int, err error) {
125+
logrus.Debugf("relayWriter: Broadcasting %d bytes to %s", len(p), rw.stream.MountName)
126+
rw.stream.Broadcast(p, rw.relay)
127+
return len(p), nil
128+
}
129+
130+
func (wm *WebRTCManager) HandleSourceOffer(mount string, offer webrtc.SessionDescription) (*webrtc.SessionDescription, error) {
131+
peerConnection, err := wm.api.NewPeerConnection(webrtc.Configuration{
132+
ICEServers: []webrtc.ICEServer{
133+
{URLs: []string{"stun:stun.l.google.com:19302"}},
134+
},
135+
})
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
141+
logrus.Infof("WebRTC Source: Received track %s from %s", track.ID(), mount)
142+
143+
stream := wm.relay.GetOrCreateStream(mount)
144+
stream.mu.Lock()
145+
stream.ContentType = "audio/ogg"
146+
stream.IsOggStream = true
147+
stream.SourceIP = "webrtc-source"
148+
// Reset page offsets so we don't sync to old pages from a previous session
149+
stream.PageOffsets = make([]int64, 128)
150+
stream.PageIndex = 0
151+
stream.OggHeaderOffset = stream.Buffer.Head
152+
stream.mu.Unlock()
153+
154+
// Capture headers written by oggwriter.NewWith
155+
var headerBuf bytes.Buffer
156+
multi := io.MultiWriter(&headerBuf, &relayWriter{wm.relay, stream})
157+
158+
writer, err := oggwriter.NewWith(multi, 48000, 2)
159+
if err != nil {
160+
logrus.WithError(err).Error("Failed to create Ogg writer for WebRTC source")
161+
return
162+
}
163+
defer writer.Close()
164+
165+
// Save captured headers immediately
166+
stream.mu.Lock()
167+
h := make([]byte, headerBuf.Len())
168+
copy(h, headerBuf.Bytes())
169+
stream.OggHead = h
170+
logrus.Infof("WebRTC Source: Captured %d bytes of Ogg/Opus headers at offset %d", len(h), stream.OggHeaderOffset)
171+
stream.mu.Unlock()
172+
173+
for {
174+
rtpPacket, _, err := track.ReadRTP()
175+
if err != nil {
176+
if err != io.EOF {
177+
logrus.WithError(err).Error("Error reading RTP packet from source")
178+
}
179+
return
180+
}
181+
if err := writer.WriteRTP(rtpPacket); err != nil {
182+
logrus.WithError(err).Error("Error writing RTP to Ogg muxer")
183+
return
184+
}
185+
}
186+
})
187+
188+
if err = peerConnection.SetRemoteDescription(offer); err != nil {
189+
return nil, err
190+
}
191+
192+
answer, err := peerConnection.CreateAnswer(nil)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
198+
if err = peerConnection.SetLocalDescription(answer); err != nil {
199+
return nil, err
200+
}
201+
<-gatherComplete
202+
203+
return peerConnection.LocalDescription(), nil
204+
}
205+
118206
func (wm *WebRTCManager) streamToTrack(pc *webrtc.PeerConnection, track *webrtc.TrackLocalStaticSample, stream *Stream) {
119207
ctx, cancel := context.WithCancel(context.Background())
120208
defer cancel()

server/server.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ func (s *Server) dispatchWebhook(event string, data map[string]interface{}) {
287287
func (s *Server) setupRoutes() *http.ServeMux {
288288
mux := http.NewServeMux()
289289
mux.HandleFunc("/admin", s.handleAdmin)
290+
mux.HandleFunc("/admin/golive", s.handleGoLive)
291+
mux.HandleFunc("/admin/golive/chunk", s.handleGoLiveChunk)
290292
mux.HandleFunc("/admin/add-mount", s.handleAddMount)
291293
mux.HandleFunc("/admin/toggle-latency", s.handleToggleLatency)
292294
mux.HandleFunc("/admin/stats", s.handleStats)
@@ -344,6 +346,7 @@ func (s *Server) setupRoutes() *http.ServeMux {
344346
mux.HandleFunc("/logout", s.handleLogout)
345347
mux.HandleFunc("/explore", s.handleExplore)
346348
mux.HandleFunc("/webrtc/offer", s.handleWebRTCOffer)
349+
mux.HandleFunc("/webrtc/source-offer", s.handleWebRTCSourceOffer)
347350
mux.HandleFunc("/player/", s.handlePlayer)
348351
mux.HandleFunc("/player-webrtc/", s.handleWebRTCPlayer)
349352
mux.HandleFunc("/embed/", s.handleEmbed)
@@ -3573,3 +3576,94 @@ func (s *Server) handleInsights(w http.ResponseWriter, r *http.Request) {
35733576
w.Header().Set("Content-Type", "application/json")
35743577
json.NewEncoder(w).Encode(stats)
35753578
}
3579+
3580+
func (s *Server) handleGoLive(w http.ResponseWriter, r *http.Request) {
3581+
user, ok := s.checkAuth(r)
3582+
if !ok {
3583+
http.Redirect(w, r, "/login", http.StatusSeeOther)
3584+
return
3585+
}
3586+
3587+
csrf := ""
3588+
if cookie, err := r.Cookie("sid"); err == nil {
3589+
s.sessionsMu.RLock()
3590+
if sess, ok := s.sessions[cookie.Value]; ok {
3591+
csrf = sess.CSRFToken
3592+
}
3593+
s.sessionsMu.RUnlock()
3594+
}
3595+
3596+
data := map[string]interface{}{
3597+
"Config": s.Config,
3598+
"User": user,
3599+
"Version": s.Version,
3600+
"CSRFToken": csrf,
3601+
}
3602+
3603+
w.Header().Set("Content-Type", "text/html")
3604+
if err := s.tmpl.ExecuteTemplate(w, "go_live.html", data); err != nil {
3605+
logrus.WithError(err).Error("Go Live template error")
3606+
}
3607+
}
3608+
3609+
func (s *Server) handleGoLiveChunk(w http.ResponseWriter, r *http.Request) {
3610+
if r.Method != http.MethodPost {
3611+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
3612+
return
3613+
}
3614+
3615+
mount := r.URL.Query().Get("mount")
3616+
if mount == "" {
3617+
http.Error(w, "mount query param required", http.StatusBadRequest)
3618+
return
3619+
}
3620+
3621+
// We don't check CSRF here because it's a data stream endpoint
3622+
// but we SHOULD check the session sid
3623+
if _, ok := s.checkAuth(r); !ok {
3624+
http.Error(w, "Unauthorized", http.StatusUnauthorized)
3625+
return
3626+
}
3627+
3628+
body, err := io.ReadAll(r.Body)
3629+
if err != nil {
3630+
http.Error(w, err.Error(), http.StatusInternalServerError)
3631+
return
3632+
}
3633+
3634+
stream := s.Relay.GetOrCreateStream(mount)
3635+
stream.SourceIP = "webaudio-http"
3636+
stream.Broadcast(body, s.Relay)
3637+
3638+
w.WriteHeader(http.StatusOK)
3639+
}
3640+
3641+
func (s *Server) handleWebRTCSourceOffer(w http.ResponseWriter, r *http.Request) {
3642+
if r.Method != http.MethodPost {
3643+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
3644+
return
3645+
}
3646+
3647+
mount := r.URL.Query().Get("mount")
3648+
if mount == "" {
3649+
http.Error(w, "mount query param required", http.StatusBadRequest)
3650+
return
3651+
}
3652+
3653+
var offer webrtc.SessionDescription
3654+
if err := json.NewDecoder(r.Body).Decode(&offer); err != nil {
3655+
http.Error(w, err.Error(), http.StatusBadRequest)
3656+
return
3657+
}
3658+
3659+
answer, err := s.WebRTCM.HandleSourceOffer(mount, offer)
3660+
if err != nil {
3661+
w.Header().Set("Content-Type", "application/json")
3662+
w.WriteHeader(http.StatusBadRequest)
3663+
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
3664+
return
3665+
}
3666+
3667+
w.Header().Set("Content-Type", "application/json")
3668+
json.NewEncoder(w).Encode(answer)
3669+
}

server/templates/admin.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
<div style="margin: 1rem 0 0.5rem 0.8rem; font-size: 0.65rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px;">Broadcasting</div>
102102
<button class="nav-link" onclick="showTab('tab-stations')"><i data-lucide="tower-control"></i> Your Stations</button>
103103
<button class="nav-link" onclick="showTab('tab-streamer')"><i data-lucide="play-circle"></i> AutoDJ</button>
104+
<a href="/admin/golive" id="debug-golive-link" class="nav-link" style="text-decoration: none; display: none;"><i data-lucide="mic"></i> GO LIVE</a>
104105

105106
{{if eq .User.Role "superadmin"}}
106107
<button class="nav-link" onclick="showTab('tab-relays')"><i data-lucide="refresh-ccw"></i> Edge Relays</button>
@@ -1263,6 +1264,8 @@ <h2>Edit AutoDJ</h2>
12631264
if (isDebug) {
12641265
const debugBar = document.getElementById('debug-stats-bar');
12651266
if (debugBar) debugBar.style.display = 'flex';
1267+
const goLiveLink = document.getElementById('debug-golive-link');
1268+
if (goLiveLink) goLiveLink.style.display = 'flex';
12661269
}
12671270

12681271
function resize() { var rect = canvas.getBoundingClientRect(); canvas.width = rect.width * 2; canvas.height = rect.height * 2; ctx.scale(2, 2); }

server/templates/autodj_studio.html

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,6 @@
120120
</style>
121121
</head>
122122
<body>
123-
<div id="loading-overlay" style="position:fixed; top:0; left:0; width:100%; height:100%; background:var(--bg); display:flex; align-items:center; justify-content:center; z-index:9999;">
124-
<div style="text-align:center;">
125-
<i data-lucide="loader-2" class="spinner" style="width:48px; height:48px; color:var(--primary);"></i>
126-
<p style="margin-top:1rem; color:var(--text-dim);">Initializing Studio...</p>
127-
</div>
128-
</div>
129-
130123
<header>
131124
<div style="display: flex; align-items: center; gap: 1.5rem;">
132125
<a href="/admin#tab-streamer" class="studio-logo">
@@ -238,7 +231,6 @@ <h2>Library</h2>
238231
let lastSong = "";
239232

240233
window.addEventListener('load', () => {
241-
document.getElementById('loading-overlay').style.display = 'none';
242234
loadLibrary();
243235
lucide.createIcons();
244236
});

0 commit comments

Comments
 (0)