Skip to content

Commit 3c70866

Browse files
author
Datanoise
committed
fix: ensure transcoder correctly waits for source and capture persistence headers for new listeners
1 parent 9e803c4 commit 3c70866

4 files changed

Lines changed: 62 additions & 17 deletions

File tree

relay/relay.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ type Stream struct {
8787
CurrentSong string
8888
Public bool
8989
Visible bool
90+
IsTranscoded bool // True if this stream is an output of a transcoder
9091

9192
LastDataReceived time.Time
9293

@@ -180,6 +181,15 @@ func (r *Relay) RemoveStream(mount string) {
180181
}
181182
}
182183

184+
func (r *Relay) GetStreamVisibility(mount string) bool {
185+
r.mu.RLock()
186+
defer r.mu.RUnlock()
187+
if s, ok := r.Streams[mount]; ok {
188+
return s.Visible
189+
}
190+
return false
191+
}
192+
183193
// GetStream safely retrieves a stream
184194
func (r *Relay) GetStream(mount string) (*Stream, bool) {
185195
r.mu.RLock()
@@ -355,6 +365,7 @@ type StreamStats struct {
355365
CurrentSong string
356366
Public bool
357367
Visible bool
368+
IsTranscoded bool
358369
ListenersCount int
359370
Uptime string
360371
Health float64
@@ -410,6 +421,7 @@ func (s *Stream) Snapshot() StreamStats {
410421
CurrentSong: s.CurrentSong,
411422
Public: s.Public,
412423
Visible: s.Visible,
424+
IsTranscoded: s.IsTranscoded,
413425
ListenersCount: len(s.listeners),
414426
Uptime: s.uptimeLocked(),
415427
Health: health,

relay/transcode.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,29 @@ func (tm *TranscoderManager) runTranscoder(ctx context.Context, inst *Transcoder
110110
}
111111

112112
func (tm *TranscoderManager) performTranscode(ctx context.Context, inst *TranscoderInstance) {
113-
input, ok := tm.relay.GetStream(inst.Config.InputMount)
114-
if !ok {
115-
return
113+
var input *Stream
114+
var ok bool
115+
116+
// 1. Wait for input stream to become available
117+
for {
118+
input, ok = tm.relay.GetStream(inst.Config.InputMount)
119+
if ok {
120+
break
121+
}
122+
select {
123+
case <-ctx.Done():
124+
return
125+
case <-time.After(1 * time.Second):
126+
// Keep waiting
127+
}
116128
}
117129

118-
// 1. Subscribe to input
130+
logrus.Infof("Transcoder %s: Input stream %s found, initializing...", inst.Config.Name, inst.Config.InputMount)
131+
132+
// 2. Subscribe to input
119133
id := fmt.Sprintf("transcoder-%s", inst.Config.Name)
120-
offset, signal := input.Subscribe(id, 0) // No burst for transcoder
134+
// We use a small burst to ensure the decoder gets enough data to start
135+
offset, signal := input.Subscribe(id, 32*1024)
121136
defer input.Unsubscribe(id)
122137

123138
reader := &StreamReader{
@@ -128,29 +143,26 @@ func (tm *TranscoderManager) performTranscode(ctx context.Context, inst *Transco
128143
ID: id,
129144
}
130145

131-
// 2. Decode (assuming MP3 input for now as standard)
146+
// 3. Decode
132147
decoder, err := mp3.NewDecoder(reader)
133148
if err != nil {
134149
logrus.WithError(err).Errorf("Transcoder %s: Failed to initialize decoder for input %s", inst.Config.Name, inst.Config.InputMount)
135150
return
136151
}
137152

138-
// 3. Create Output Stream
153+
// 4. Create Output Stream
139154
output := tm.relay.GetOrCreateStream(inst.Config.OutputMount)
140155
output.Name = fmt.Sprintf("%s (%s %dK)", input.Name, inst.Config.Format, inst.Config.Bitrate)
141156
output.Bitrate = fmt.Sprintf("%d", inst.Config.Bitrate)
142-
output.ContentType = "audio/mpeg"
143-
if inst.Config.Format == "opus" {
144-
output.ContentType = "audio/ogg"
145-
}
146-
147-
// 4. Encode & Broadcast
157+
output.IsTranscoded = true
158+
output.Visible = tm.relay.GetStreamVisibility(inst.Config.InputMount) // Follow input visibility
159+
148160
if inst.Config.Format == "mp3" {
161+
output.ContentType = "audio/mpeg"
149162
tm.encodeMP3(ctx, inst, decoder, output)
150163
} else if inst.Config.Format == "opus" {
164+
output.ContentType = "audio/ogg"
151165
tm.encodeOpus(ctx, inst, decoder, output)
152-
} else {
153-
logrus.Warnf("Transcoding format %s not yet implemented", inst.Config.Format)
154166
}
155167
}
156168

server/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,7 @@ type streamEventInfo struct {
16601660
BytesDropped int64 `json:"bytes_dropped"`
16611661
CurrentSong string `json:"song"`
16621662
Health float64 `json:"health"`
1663+
IsTranscoded bool `json:"is_transcoded"`
16631664
}
16641665

16651666
type relayEventInfo struct {
@@ -1683,7 +1684,7 @@ func (s *Server) collectStatsPayload(user *config.User) ([]byte, error) {
16831684
Mount: st.MountName, Name: st.Name, Listeners: lc, Bitrate: st.Bitrate,
16841685
Uptime: st.Uptime, ContentType: st.ContentType, SourceIP: st.SourceIP,
16851686
BytesIn: st.BytesIn, BytesOut: st.BytesOut, BytesDropped: st.BytesDropped,
1686-
CurrentSong: st.CurrentSong, Health: st.Health,
1687+
CurrentSong: st.CurrentSong, Health: st.Health, IsTranscoded: st.IsTranscoded,
16871688
})
16881689
if st.SourceIP == "relay-pull" {
16891690
tr++

server/templates/admin.html

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
.role-badge { font-size: 0.7rem; padding: 0.1rem 0.4rem; border-radius: 4px; font-weight: 800; text-transform: uppercase; margin-left: 0.5rem; }
5151
.role-super { background: var(--primary); color: var(--bg); }
5252
.role-admin { background: var(--border); color: var(--text); }
53+
.badge-transcoded { background: rgba(168, 85, 247, 0.1); color: #a855f7; font-size: 0.6rem; padding: 0.1rem 0.4rem; border-radius: 4px; font-weight: 800; border: 1px solid rgba(168, 85, 247, 0.2); margin-left: 0.5rem; text-transform: uppercase; }
5354
.status-pill { display: inline-flex; align-items: center; gap: 0.4rem; background: rgba(16, 185, 129, 0.1); color: var(--success); padding: 0.2rem 0.6rem; border-radius: 100px; font-size: 0.75rem; font-weight: 700; }
5455
.status-pill.inactive { color: var(--text-dim); background: rgba(255,255,255,0.05); }
5556
.dot { width: 6px; height: 6px; background: currentColor; border-radius: 50%; }
@@ -564,7 +565,26 @@ <h2>Active Transcoders</h2>
564565

565566
data.streams.forEach(function(s) {
566567
var prev = streamHistory[s.mount] || { in: s.bytes_in, out: s.bytes_out }; var curIn = (s.bytes_in - prev.in) / 1024, curOut = (s.bytes_out - prev.out) / 1024; streamHistory[s.mount] = { in: s.bytes_in, out: s.bytes_out };
567-
var clone = rowTemplate.content.cloneNode(true); clone.querySelector('.row-mount').textContent = s.mount; clone.querySelector('.row-ip').textContent = s.ip; clone.querySelector('.row-song').textContent = s.song;
568+
var clone = rowTemplate.content.cloneNode(true);
569+
570+
var mountCell = clone.querySelector('.row-mount');
571+
mountCell.textContent = s.mount;
572+
if (s.ip === 'relay-pull') {
573+
var badge = document.createElement('span');
574+
badge.className = 'badge-transcoded';
575+
badge.style.background = 'rgba(56, 189, 248, 0.1)';
576+
badge.style.color = '#38bdf8';
577+
badge.style.borderColor = 'rgba(56, 189, 248, 0.2)';
578+
badge.textContent = 'RELAY';
579+
mountCell.parentElement.appendChild(badge);
580+
} else if (s.is_transcoded) {
581+
var badge = document.createElement('span');
582+
badge.className = 'badge-transcoded';
583+
badge.textContent = 'TRANSCODED';
584+
mountCell.parentElement.appendChild(badge);
585+
}
586+
587+
clone.querySelector('.row-ip').textContent = s.ip; clone.querySelector('.row-song').textContent = s.song;
568588
clone.querySelector('.row-listeners').textContent = s.listeners; clone.querySelector('.row-in').textContent = curIn.toFixed(1); clone.querySelector('.row-out').textContent = curOut.toFixed(1);
569589

570590
var health = s.health;

0 commit comments

Comments
 (0)