@@ -3,6 +3,7 @@ package relay
33import (
44 "sync"
55 "testing"
6+ "time"
67)
78
89func TestSubscribeOggEmptyPageOffsetsFallsBackToBurst (t * testing.T ) {
@@ -81,3 +82,128 @@ func TestSubscribeAfterCloseRejects(t *testing.T) {
8182 t .Fatal ("expected SubscribeSafe to return false after Close" )
8283 }
8384}
85+
86+ // TestSubscribeSkipsBurstWhenSourceIdle is the regression test for the
87+ // stale-burst replay bug: when the producer has been silent for >2s, the
88+ // bytes in the buffer are stale and a new subscriber must start at Head
89+ // rather than being bursted with that old audio.
90+ func TestSubscribeSkipsBurstWhenSourceIdle (t * testing.T ) {
91+ r := NewRelay (false , nil )
92+ s := r .GetOrCreateStream ("/idle" )
93+
94+ s .Broadcast (make ([]byte , 8192 ), r )
95+ // Backdate LastDataReceived past the freshness window.
96+ s .mu .Lock ()
97+ s .LastDataReceived = time .Now ().Add (- 10 * time .Second )
98+ s .mu .Unlock ()
99+
100+ head := s .Buffer .Head
101+ start , _ := s .Subscribe ("listener-idle" , 4096 )
102+ if start != head {
103+ t .Fatalf ("idle-source Subscribe should start at Head=%d, got %d (would replay %d stale bytes)" ,
104+ head , start , head - start )
105+ }
106+ }
107+
108+ // TestSubscribeBurstsWhenSourceFresh confirms the normal burst path still
109+ // works on an active stream — the freshness gate must not break instant
110+ // playback for the common case.
111+ func TestSubscribeBurstsWhenSourceFresh (t * testing.T ) {
112+ r := NewRelay (false , nil )
113+ s := r .GetOrCreateStream ("/fresh" )
114+
115+ // Broadcast sets LastDataReceived to time.Now() itself.
116+ s .Broadcast (make ([]byte , 8192 ), r )
117+
118+ head := s .Buffer .Head
119+ start , _ := s .Subscribe ("listener-fresh" , 4096 )
120+ if start >= head {
121+ t .Fatalf ("fresh-source Subscribe should burst, start=%d head=%d" , start , head )
122+ }
123+ if head - start != 4096 {
124+ t .Fatalf ("expected 4096-byte burst, got %d" , head - start )
125+ }
126+ }
127+
128+ // TestBroadcastIgnoresFalseOggMagic is the regression test for the false-
129+ // positive Ogg page detection bug. A "OggS" byte sequence followed by a
130+ // non-zero byte (a real Opus payload pattern) must NOT be recorded into
131+ // PageOffsets; a real header (version byte 0) must.
132+ func TestBroadcastIgnoresFalseOggMagic (t * testing.T ) {
133+ r := NewRelay (false , nil )
134+ s := r .GetOrCreateStream ("/ogg-validation" )
135+ s .ContentType = "audio/ogg"
136+ s .IsOggStream = true
137+
138+ // False positive: "OggS" + 0xFF (version != 0). Must not be tracked.
139+ payload := make ([]byte , 64 )
140+ payload [10 ] = 'O'
141+ payload [11 ] = 'g'
142+ payload [12 ] = 'g'
143+ payload [13 ] = 'S'
144+ payload [14 ] = 0xFF
145+ s .Broadcast (payload , r )
146+
147+ for i , po := range s .PageOffsets {
148+ if po != 0 {
149+ t .Fatalf ("PageOffsets[%d]=%d — a false-positive OggS was tracked" , i , po )
150+ }
151+ }
152+
153+ // Real header: "OggS" + 0x00. Must be tracked.
154+ real := make ([]byte , 32 )
155+ real [5 ] = 'O'
156+ real [6 ] = 'g'
157+ real [7 ] = 'g'
158+ real [8 ] = 'S'
159+ real [9 ] = 0x00
160+ expected := s .Buffer .Head + 5
161+ s .Broadcast (real , r )
162+
163+ if s .LastPageOffset != expected {
164+ t .Fatalf ("LastPageOffset=%d, expected %d" , s .LastPageOffset , expected )
165+ }
166+ }
167+
168+ // TestBeginSessionWipesOggState is the regression test for the transcoder-
169+ // restart bug: a new producer session must clear PageOffsets, LastPageOffset,
170+ // OggHead, OggHeaderOffset so new subscribers can't align to stale bytes
171+ // from a previous Ogg serial. MinListenerOffset must also snap to Head so
172+ // existing listeners jump forward on their next flushGen check.
173+ func TestBeginSessionWipesOggState (t * testing.T ) {
174+ r := NewRelay (false , nil )
175+ s := r .GetOrCreateStream ("/session" )
176+ s .ContentType = "audio/ogg"
177+ s .IsOggStream = true
178+
179+ s .StoreOggHead ([]byte {1 , 2 , 3 , 4 }, 100 )
180+ s .mu .Lock ()
181+ s .LastPageOffset = 999
182+ s .PageOffsets [0 ] = 999
183+ s .PageIndex = 7
184+ s .mu .Unlock ()
185+
186+ beforeGen := s .FlushGen ()
187+ s .BeginSession ()
188+
189+ if got := s .GetOggHead (); got != nil {
190+ t .Fatalf ("OggHead should be nil after BeginSession, got %v" , got )
191+ }
192+ s .mu .RLock ()
193+ defer s .mu .RUnlock ()
194+ if s .LastPageOffset != 0 {
195+ t .Fatalf ("LastPageOffset should be reset, got %d" , s .LastPageOffset )
196+ }
197+ if s .PageOffsets [0 ] != 0 {
198+ t .Fatalf ("PageOffsets[0] should be reset, got %d" , s .PageOffsets [0 ])
199+ }
200+ if s .PageIndex != 0 {
201+ t .Fatalf ("PageIndex should be reset, got %d" , s .PageIndex )
202+ }
203+ if s .MinListenerOffset != s .Buffer .Head {
204+ t .Fatalf ("MinListenerOffset=%d should equal Head=%d" , s .MinListenerOffset , s .Buffer .Head )
205+ }
206+ if s .flushGen .Load () <= beforeGen {
207+ t .Fatalf ("flushGen should have advanced (was %d, now %d)" , beforeGen , s .flushGen .Load ())
208+ }
209+ }
0 commit comments