title: Seamless video loop in gstreamer
body: <p>I'm trying to loop the video playback using gstreamer and it's python bindings.  First attempt was to hook EOS <em>message</em> and generate seek message for the pipeline:</p>
  
  <pre><code>import gi
  gi.require_version("Gst", "1.0")
  from gi.repository import Gst
  
  import time
  
  if not Gst.init_check()[0]:
      print("gstreamer initialization failed")
  
  source0 = Gst.ElementFactory.make("filesrc", "source0")
  assert source0 is not None
  source0.set_property("location", "video0.mp4")
  
  qtdemux0 = Gst.ElementFactory.make("qtdemux", "demux0")
  assert qtdemux0 is not None
  
  decoder0 = Gst.ElementFactory.make("nxvideodec", "video_decoder0")
  assert decoder0 is not None
  
  def demux0_pad_added(demux, pad):
      if pad.name == 'video_0':  # We expect exactly first one video stream
          pad.link(decoder0.get_static_pad("sink"))
  
  qtdemux0.connect("pad-added", demux0_pad_added)
  
  video_sink = Gst.ElementFactory.make("nxvideosink", "video_sink")
  assert video_sink is not None
  
  pipeline0 = Gst.Pipeline()
  assert pipeline0 is not None
  pipeline0.add(source0)
  pipeline0.add(qtdemux0)
  pipeline0.add(decoder0)
  pipeline0.add(video_sink)
  
  source0.link(qtdemux0)
  """qtdemux0 -&gt; decoder0 dynamic linking"""
  decoder0.link(video_sink)
  
  ######################################################
  
  def main():
      message_bus = pipeline0.get_bus()
      pipeline0.set_state(Gst.State.PLAYING)
  
      while True:
          if message_bus.have_pending():  # Working without glib mainloop
              message = message_bus.pop()
              if message.type == Gst.MessageType.EOS:  # End-Of-Stream: loop the video, seek to beginning
                  pipeline0.seek(1.0,
                                Gst.Format.TIME,
                                Gst.SeekFlags.FLUSH,
                                Gst.SeekType.SET, 0,
                                Gst.SeekType.NONE, 0)
              elif message.type == Gst.MessageType.ERROR:
                  print("ERROR", message)
                  break
          time.sleep(0.01) # Tried 0.001 - same result
  
  if __name__ == "__main__":
      main()
  </code></pre>
  
  <p>And it actually works quite fine except one thing - seek to the beginning is not really seamless. I can see tiny glitch. Because the video is an infinite animation this tiny glitch actually become noticeable. My second attempt was to use queue for decoded frames and hook EOS <em>event</em>:</p>
  
  <pre><code>import gi
  gi.require_version("Gst", "1.0")
  from gi.repository import Gst
  
  import time
  
  if not Gst.init_check()[0]:
      print("gstreamer initialization failed")
  
  source0 = Gst.ElementFactory.make("filesrc", "source0")
  assert source0 is not None
  source0.set_property("location", "video0.mp4")
  
  qtdemux0 = Gst.ElementFactory.make("qtdemux", "demux0")
  assert qtdemux0 is not None
  
  decoder0 = Gst.ElementFactory.make("nxvideodec", "video_decoder0")
  assert decoder0 is not None
  
  def demux0_pad_added(demux, pad):
      if pad.name == 'video_0':  # We expect exactly first one video stream
          pad.link(decoder0.get_static_pad("sink"))
  
  qtdemux0.connect("pad-added", demux0_pad_added)
  
  queue = Gst.ElementFactory.make("queue", "queue")
  assert queue is not None
  video_sink = Gst.ElementFactory.make("nxvideosink", "video_sink")
  assert video_sink is not None
  
  pipeline0 = Gst.Pipeline()
  assert pipeline0 is not None
  pipeline0.add(source0)
  pipeline0.add(qtdemux0)
  pipeline0.add(decoder0)
  pipeline0.add(queue)
  pipeline0.add(video_sink)
  
  source0.link(qtdemux0)
  """qtdemux0 -&gt; decoder0 dynamic linking"""
  decoder0.link(queue)
  queue.link(video_sink)
  
  ######################################################
  
  def cb_event(pad, info, *user_data):
      event = info.get_event()
      if event is not None and event.type == Gst.EventType.EOS:
          decoder0.seek(1.0,
                        Gst.Format.TIME,
                        Gst.SeekFlags.FLUSH,
                        Gst.SeekType.SET, 0,
                        Gst.SeekType.NONE, 0)
          return Gst.PadProbeReturn.DROP
      return Gst.PadProbeReturn.PASS
  
  def main():
      dec0_src_pad = decoder0.get_static_pad("src")
      dec0_src_pad.add_probe(Gst.PadProbeType.BLOCK | Gst.PadProbeType.EVENT_DOWNSTREAM, cb_event)
  
      message_bus = pipeline0.get_bus()
      pipeline0.set_state(Gst.State.PLAYING)
  
      while True:
          # do nothing
          time.sleep(1)
  
  if __name__ == "__main__":
      main()
  </code></pre>
  
  <p>After the first EOS event the playback is just stalled. I've tried several different things like: <em>pass</em> the EOS event, <em>drop</em> EOS and add offset to the decoder's source pad, send seek event to the pipeline itself and others. But I can't get it work. </p>
  
  <p>In an effort to understand I also tried to enable debug mode and write my own kinda logger of pipeline activity using pad probes. Debug mode was not very useful, the log is very bulky and missing some details. My own log includes upstream/downstream events and the buffers timing information. However, I still can not understand what is wrong and how to get it to work.</p>
  
  <p>Obviously I not just missing something but do not understand some fundamental thing about how gstreamer pipeline works.</p>
  
  <p><strong>So, the question is:</strong> What should I do with the second version of code to get it work? <br>Additional question: Are there some tools or techniques to get clear idea of what is happening inside the pipeline and its contained elements?</p>
  
  <p>I will very appreciate detailed answers. It is more important for me to <strong><em>understand</em></strong> what I am doing wrong than to just bring the program to work.</p>
  
  <p><strong><em>p.s.</strong> Program is run under GNU/Linux on the NanoPi S2 board. Video is stored in the MP4 container (without audio) and compressed with h264. Please feel free to post code samples in any language, not necessarily Python.</em></p>