@@ -774,6 +774,49 @@ def try_pending_renegotiation(self):
774774 self .log ("Still not in stable state, will retry" )
775775 return True # Continue the timer
776776
777+ def check_hls_streams_ready (self ):
778+ """Check if both audio and video are connected and unblock if ready"""
779+ if not self .use_hls :
780+ return
781+
782+ # Check if both streams are connected
783+ if hasattr (self , 'hls_audio_connected' ) and self .hls_audio_connected and \
784+ hasattr (self , 'hls_video_connected' ) and self .hls_video_connected :
785+
786+ self .log (" ✅ Both audio and video connected - starting HLS recording" )
787+
788+ # First sync the HLS sink state
789+ if hasattr (self , 'hlssink' ) and self .hlssink :
790+ ret = self .hlssink .sync_state_with_parent ()
791+ self .log (f" HLS sink sync result: { ret } " )
792+
793+ # Ensure it's in PLAYING state
794+ state_ret , state , pending = self .hlssink .get_state (0 )
795+ if state != Gst .State .PLAYING :
796+ self .log (f" HLS sink not in PLAYING state ({ state } ), forcing to PLAYING" )
797+ ret = self .hlssink .set_state (Gst .State .PLAYING )
798+ self .log (f" HLS sink set_state result: { ret } " )
799+
800+ # Wait for state change
801+ timeout = 2 * Gst .SECOND
802+ state_ret , state , pending = self .hlssink .get_state (timeout )
803+ self .log (f" HLS sink final state: { state } " )
804+
805+ # Now remove the blocking probes to allow data flow
806+ if hasattr (self , 'video_probe_id' ) and hasattr (self , 'video_queue' ):
807+ video_src = self .video_queue .get_static_pad ('src' )
808+ if video_src :
809+ video_src .remove_probe (self .video_probe_id )
810+ self .log (" ▶️ Unblocked video flow" )
811+
812+ if hasattr (self , 'audio_probe_id' ) and hasattr (self , 'audio_queue' ):
813+ audio_src = self .audio_queue .get_static_pad ('src' )
814+ if audio_src :
815+ audio_src .remove_probe (self .audio_probe_id )
816+ self .log (" ▶️ Unblocked audio flow" )
817+
818+ self .log (" 🎬 HLS recording started!" )
819+
777820 def setup_hls_muxer (self ):
778821 """Set up shared mpegtsmux and HLS sink for audio/video"""
779822 if hasattr (self , 'hls_mux' ):
@@ -834,8 +877,8 @@ def setup_hls_muxer(self):
834877 # Add sink to pipeline
835878 self .pipe .add (self .hlssink )
836879
837- # Don't sync state yet - wait until pads are connected
838- # This prevents the muxer from starting before it has inputs
880+ # Keep sink in NULL state until pads are connected
881+ self . hlssink . set_state ( Gst . State . NULL )
839882
840883 self .log (" ✅ HLS muxer ready for audio/video streams" )
841884
@@ -1538,6 +1581,8 @@ def ndi_video_probe_cb(pad, info):
15381581 if not video_queue :
15391582 self .log ("Failed to create video queue for HLS" , "error" )
15401583 return
1584+ # Store reference for later access
1585+ self .video_queue = video_queue
15411586
15421587 # Add identity element to help with segment handling
15431588 video_identity = Gst .ElementFactory .make ('identity' , 'video_identity' )
@@ -1628,21 +1673,9 @@ def depay_probe_cb(pad, info):
16281673 h264_pad = h264parse .get_static_pad ('src' )
16291674 if h264_pad :
16301675 def h264_probe_cb (pad , info ):
1631- # Check if this is a buffer (not an event)
1632- if info .type & Gst .PadProbeType .BUFFER :
1633- # Inject a segment event before the first buffer
1634- if not hasattr (self , '_h264_segment_sent' ):
1635- self ._h264_segment_sent = True
1636- # Create segment event
1637- segment = Gst .Segment ()
1638- segment .init (Gst .Format .TIME )
1639- event = Gst .Event .new_segment (segment )
1640- pad .push_event (event )
1641- self .log (" ✅ Injected segment event for HLS video" )
1642-
1643- if not hasattr (self , '_h264_probe_logged' ):
1644- self ._h264_probe_logged = True
1645- self .log (" ✅ Video data confirmed after h264parse!" )
1676+ if not hasattr (self , '_h264_probe_logged' ):
1677+ self ._h264_probe_logged = True
1678+ self .log (" ✅ Video data confirmed after h264parse!" )
16461679 return Gst .PadProbeReturn .OK
16471680 h264_pad .add_probe (Gst .PadProbeType .BUFFER , h264_probe_cb )
16481681
@@ -1663,13 +1696,6 @@ def queue_probe_cb(pad, info):
16631696 if video_pad :
16641697 src_pad = video_queue .get_static_pad ('src' )
16651698
1666- # Send initial segment event before linking
1667- segment = Gst .Segment ()
1668- segment .init (Gst .Format .TIME )
1669- event = Gst .Event .new_segment (segment )
1670- video_pad .send_event (event )
1671- self .log (" ✅ Sent initial segment event to HLS video pad" )
1672-
16731699 if src_pad .link (video_pad ) == Gst .PadLinkReturn .OK :
16741700 self .log (" ✅ Video connected to HLS sink" )
16751701 # Check hlssink state
@@ -1703,22 +1729,23 @@ def queue_probe_cb(pad, info):
17031729 for element in elements :
17041730 element .sync_state_with_parent ()
17051731
1706- # Also ensure HLS sink is in correct state
1707- if hasattr (self , 'hlssink' ) and self .hlssink :
1708- # First try to sync with parent
1709- ret = self .hlssink .sync_state_with_parent ()
1710- self .log (f" HLS sink sync result: { ret } " )
1711-
1712- # Check the actual state
1713- state_ret , state , pending = self .hlssink .get_state (0 )
1714- if state != Gst .State .PLAYING :
1715- self .log (f" HLS sink not in PLAYING state ({ state } ), forcing to PLAYING" )
1716- ret = self .hlssink .set_state (Gst .State .PLAYING )
1717- self .log (f" HLS sink set_state result: { ret } " )
1718- # Wait for state change to complete
1719- timeout = 5 * Gst .SECOND # 5 second timeout
1720- state_ret , state , pending = self .hlssink .get_state (timeout )
1721- self .log (f" HLS sink final state: { state } (result: { state_ret } )" )
1732+ # Add blocking probe to video queue to prevent data flow until ready
1733+ if self .use_hls :
1734+ video_queue_src = video_queue .get_static_pad ('src' )
1735+ if video_queue_src :
1736+ def video_block_probe_cb (pad , info ):
1737+ return Gst .PadProbeReturn .OK # Just block
1738+ self .video_probe_id = video_queue_src .add_probe (
1739+ Gst .PadProbeType .BLOCK_DOWNSTREAM ,
1740+ video_block_probe_cb
1741+ )
1742+ self .log (" 🛑 Blocking video flow until both streams ready" )
1743+
1744+ # Track that video is connected
1745+ self .hls_video_connected = True
1746+
1747+ # Check if both streams are ready to start
1748+ self .check_hls_streams_ready ()
17221749
17231750 # Link incoming pad to queue
17241751 sink_pad = queue .get_static_pad ('sink' )
@@ -1957,6 +1984,8 @@ def handle_audio_pad(self, pad):
19571984 aacparse = Gst .ElementFactory .make ('aacparse' , None )
19581985 # Create a queue before muxer
19591986 audio_queue = Gst .ElementFactory .make ('queue' , 'audio_queue_hls' )
1987+ # Store reference for later access
1988+ self .audio_queue = audio_queue
19601989 # Add identity element for audio segment handling
19611990 audio_identity = Gst .ElementFactory .make ('identity' , 'audio_identity' )
19621991 if audio_identity :
@@ -2065,26 +2094,24 @@ def handle_audio_pad(self, pad):
20652094 for element in elements :
20662095 element .sync_state_with_parent ()
20672096
2068- # Add segment event injection for audio too
2069- audio_src_pad = audio_queue .get_static_pad ('src' )
2070- if audio_src_pad :
2071- def audio_segment_probe_cb (pad , info ):
2072- if info .type & Gst .PadProbeType .BUFFER :
2073- if not hasattr (self , '_audio_segment_sent' ):
2074- self ._audio_segment_sent = True
2075- # Inject segment event before first audio buffer
2076- segment = Gst .Segment ()
2077- segment .init (Gst .Format .TIME )
2078- event = Gst .Event .new_segment (segment )
2079- pad .push_event (event )
2080- self .log (" ✅ Injected segment event for HLS audio" )
2081- return Gst .PadProbeReturn .OK
2082- audio_src_pad .add_probe (Gst .PadProbeType .BUFFER , audio_segment_probe_cb )
20832097
2084- # Also ensure HLS sink is in correct state for audio
2085- if hasattr (self , 'hlssink' ) and self .hlssink :
2086- # Don't sync again if already done for video
2087- pass
2098+ # Add blocking probe to audio queue to prevent data flow until ready
2099+ if self .use_hls :
2100+ audio_queue_src = audio_queue .get_static_pad ('src' )
2101+ if audio_queue_src :
2102+ def audio_block_probe_cb (pad , info ):
2103+ return Gst .PadProbeReturn .OK # Just block
2104+ self .audio_probe_id = audio_queue_src .add_probe (
2105+ Gst .PadProbeType .BLOCK_DOWNSTREAM ,
2106+ audio_block_probe_cb
2107+ )
2108+ self .log (" 🛑 Blocking audio flow until both streams ready" )
2109+
2110+ # Track that audio is connected
2111+ self .hls_audio_connected = True
2112+
2113+ # Check if both streams are ready to start
2114+ self .check_hls_streams_ready ()
20882115
20892116 # Link incoming pad to queue
20902117 sink_pad = queue .get_static_pad ('sink' )
0 commit comments