Search code examples
pythongstreamergtk3xlib

Playing video in Gtk in a window with a menubar


I have created a video player in Gtk3 using Gstreamer in Python3. It works except when I add a GtkMenuBar (place 2). It will then either show a black screen, or fail with an exception. The exception references the XInitThreads, which I am calling (Place 1) (I took this from the pitivi project) but this does not seem to make a diffrence.

Question: How do I make this work?

Other things I would like to know:

  1. Why would the menubar break this?
  2. This will clearly break on anything not X, is there some prebuilt component the abstracts this logic and is crossplatform that I am missing?

System:

  • python3
  • Gtk3
  • Ubuntu 16.04

The exception:

[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python3: ../../src/xcb_io.c:179: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.

The code (in as small a form as possible to demonstrate the concept):

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gst', '1.0')
gi.require_version('GstVideo', '1.0')

from gi.repository import Gtk, xlib
from gi.repository import Gst, Gdk, GdkX11, GstVideo
Gst.init(None)
Gst.init_check(None)

# Place 1
from ctypes import cdll
x11 = cdll.LoadLibrary('libX11.so')
x11.XInitThreads()

# [xcb] Unknown request in queue while dequeuing
# [xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
# [xcb] Aborting, sorry about that.
# python3: ../../src/xcb_io.c:179: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.

# (foo.py:31933): Gdk-WARNING **: foo.py: Fatal IO error 11 (Resource temporarily unavailable) on X server :1.

class PipelineManager(object):
    def __init__(self, window, pipeline):
        self.window = window
        if isinstance(pipeline, str):
            pipeline = Gst.parse_launch(pipeline)

        self.pipeline = pipeline

        bus = pipeline.get_bus()
        bus.set_sync_handler(self.bus_callback)
        pipeline.set_state(Gst.State.PLAYING)

    def bus_callback(self, bus, message):
        if message.type is Gst.MessageType.ELEMENT:
            if GstVideo.is_video_overlay_prepare_window_handle_message(message):
                Gdk.threads_enter()
                Gdk.Display.get_default().sync()
                win = self.window.get_property('window')

                if isinstance(win, GdkX11.X11Window):
                    message.src.set_window_handle(win.get_xid())
                else:
                    print('Nope')

                Gdk.threads_leave()
        return Gst.BusSyncReply.PASS


pipeline = Gst.parse_launch('videotestsrc ! xvimagesink sync=false')

window = Gtk.ApplicationWindow()

header_bar = Gtk.HeaderBar()
header_bar.set_show_close_button(True)
# window.set_titlebar(header_bar)  # Place 2

drawing_area = Gtk.DrawingArea()
drawing_area.connect('realize', lambda widget: PipelineManager(widget, pipeline))
window.add(drawing_area)

window.show_all()

def on_destroy(win):
    try:
        Gtk.main_quit()
    except KeyboardInterrupt:
        pass

window.connect('destroy', on_destroy)

Gtk.main()

Solution

  • When searching through documentation on a separate issue, I came across a reference to the gtksink widget. This seems to be the correct way to put video in a gtk window, but unfortunately none of the tutorials on this use it.

    Using the gtksink widget fixes all the problems and greatly reduces code complexity.

    The revised code:

    from pprint import pprint
    
    import gi
    gi.require_version('Gtk', '3.0')
    gi.require_version('Gst', '1.0')
    gi.require_version('GstVideo', '1.0')
    
    from gi.repository import Gtk, Gst
    Gst.init(None)
    Gst.init_check(None)
    
    
    class GstWidget(Gtk.Box):
        def __init__(self, pipeline):
            super().__init__()
            self.connect('realize', self._on_realize)
            self._bin = Gst.parse_bin_from_description('videotestsrc', True)
    
        def _on_realize(self, widget):
            pipeline = Gst.Pipeline()
            factory = pipeline.get_factory()
            gtksink = factory.make('gtksink')
            pipeline.add(gtksink)
            pipeline.add(self._bin)
            self._bin.link(gtksink)
            self.pack_start(gtksink.props.widget, True, True, 0)
            gtksink.props.widget.show()
            pipeline.set_state(Gst.State.PLAYING)
    
    
    window = Gtk.ApplicationWindow()
    
    header_bar = Gtk.HeaderBar()
    header_bar.set_show_close_button(True)
    window.set_titlebar(header_bar)  # Place 2
    
    widget = GstWidget('videotestsrc')
    widget.set_size_request(200, 200)
    
    window.add(widget)
    
    window.show_all()
    
    def on_destroy(win):
        try:
            Gtk.main_quit()
        except KeyboardInterrupt:
            pass
    
    window.connect('destroy', on_destroy)
    
    Gtk.main()