Background
I'm trying to create a media player that can run even if it has no window handle to render the video into. (So you can't see the video, but can still hear sound.)
I thought I could achieve this with an output-selector: When no window handle is available, the output is redirected to a fakesink.
Code
(In order to reduce the amount of code, there is no audio playback.)
import sys
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, GLib, Gst, GstVideo
Gst.init(None)
if sys.platform == 'win32':
import ctypes
PyCapsule_GetPointer = ctypes.pythonapi.PyCapsule_GetPointer
PyCapsule_GetPointer.restype = ctypes.c_void_p
PyCapsule_GetPointer.argtypes = [ctypes.py_object]
gdkdll = ctypes.CDLL('libgdk-3-0.dll')
gdkdll.gdk_win32_window_get_handle.argtypes = [ctypes.c_void_p]
def get_window_handle(widget):
window = widget.get_window()
if not window.ensure_native():
raise Exception('video playback requires a native window')
window_gpointer = PyCapsule_GetPointer(window.__gpointer__, None)
handle = gdkdll.gdk_win32_window_get_handle(window_gpointer)
return handle
else:
def get_window_handle(widget):
return widget.get_window().get_xid()
class VideoPlayer:
def __init__(self, canvas):
self._canvas = canvas
self._setup_pipeline()
def _setup_pipeline(self):
# The element with the set_window_handle function will be stored here
self._video_overlay = None
self._pipeline = Gst.ElementFactory.make('pipeline', 'pipeline')
src = Gst.ElementFactory.make('videotestsrc', 'src')
output_selector = Gst.ElementFactory.make('output-selector', 'output-selector')
fake_video_sink = Gst.ElementFactory.make('fakevideosink', 'fakevideosink')
video_convert = Gst.ElementFactory.make('videoconvert', 'videoconvert')
auto_video_sink = Gst.ElementFactory.make('autovideosink', 'autovideosink')
output_selector.props.resend_latest = True
for element in [src, output_selector, fake_video_sink, video_convert, auto_video_sink]:
self._pipeline.add(element)
src.link(output_selector)
# Connect the output-selector to the fakesink and the videoconvert.
# By default, the fakesink is selected.
self._fake_video_sink_source_pad = output_selector.get_request_pad("src_%u")
sink_pad = fake_video_sink.get_static_pad('sink')
self._fake_video_sink_source_pad.link(sink_pad)
output_selector.props.active_pad = self._fake_video_sink_source_pad
self._video_convert_source_pad = output_selector.get_request_pad("src_%u")
sink_pad = video_convert.get_static_pad('sink')
self._video_convert_source_pad.link(sink_pad)
video_convert.link(auto_video_sink)
self._setup_signal_handlers()
def _setup_signal_handlers(self):
self._canvas.connect('realize', self._on_canvas_realize)
self._canvas.connect('unrealize', self._on_canvas_unrealize)
bus = self._pipeline.get_bus()
bus.enable_sync_message_emission()
bus.connect('sync-message::element', self._on_sync_element_message)
def _on_sync_element_message(self, bus, message):
if message.get_structure().get_name() == 'prepare-window-handle':
self._video_overlay = message.src
self._video_overlay.set_window_handle(self._canvas_window_handle)
def _on_canvas_realize(self, canvas):
self._canvas_window_handle = get_window_handle(self._canvas)
output_selector = self._pipeline.get_by_name('output-selector')
# If the fakesink is currently selected, activate the videoconvert
# so the video will start rendering.
# If the videoconvert is currently selected, update its window handle.
if output_selector.props.active_pad is self._fake_video_sink_source_pad:
output_selector.props.active_pad = self._video_convert_source_pad
elif self._video_overlay is not None:
self._video_overlay.set_window_handle(self._canvas_window_handle)
def _on_canvas_unrealize(self, canvas):
# Stop rendering; redirect output to the fakesink
output_selector = self._pipeline.get_by_name('output-selector')
output_selector.props.active_pad = self._fake_video_sink_source_pad
def start(self):
self._pipeline.set_state(Gst.State.PLAYING)
window = Gtk.Window()
canvas = Gtk.DrawingArea()
window.add(canvas)
player = VideoPlayer(canvas)
canvas.connect('realize', lambda *_: player.start())
window.connect('destroy', Gtk.main_quit)
window.show_all()
Gtk.main()
Problem
The video doesn't play. All I can see is a static image. For some reason, the pipeline never reaches the PLAYING state.
Please tell me what's wrong with my code, or suggest an alternative solution.