wpe2: New WPE plugin making use of the "WPE Platform API"
Currently only a wpevideosrc2 element is exposed. GL and SHM buffer rendering are supported, navigation events too (touch is un-tested). Audio pads handling is not supported yet (that requires new WPE API). Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8789>
This commit is contained in:
parent
7288b034ac
commit
28d97212c5
@ -1,6 +1,7 @@
|
||||
subprojects/gst-plugins-bad/ext/nvcomp
|
||||
subprojects/gst-plugins-bad/ext/nvdswrapper
|
||||
subprojects/gst-plugins-bad/ext/qt6d3d11
|
||||
subprojects/gst-plugins-bad/ext/wpe2
|
||||
subprojects/gst-plugins-bad/gst-libs/gst/cuda
|
||||
subprojects/gst-plugins-bad/gst-libs/gst/d3d11
|
||||
subprojects/gst-plugins-bad/gst-libs/gst/d3d12
|
||||
|
@ -20830,6 +20830,7 @@
|
||||
"GstWpeSrc:draw-background",
|
||||
"GstWpeSrc:location",
|
||||
"GstWpeVideoSrc!src",
|
||||
"GstWpeVideoSrc2!src",
|
||||
"GstWrapperCameraBinSrc",
|
||||
"GstWrapperCameraBinSrc!imgsrc",
|
||||
"GstWrapperCameraBinSrc!vfsrc",
|
||||
@ -39216,6 +39217,7 @@
|
||||
"element-webvttenc",
|
||||
"element-wildmididec",
|
||||
"element-wpesrc",
|
||||
"element-wpesrc2",
|
||||
"element-wrappercamerabinsrc",
|
||||
"element-x264enc",
|
||||
"element-x265enc",
|
||||
@ -69485,6 +69487,7 @@
|
||||
"plugin-winks",
|
||||
"plugin-winscreencap",
|
||||
"plugin-wpe",
|
||||
"plugin-wpe2",
|
||||
"plugin-x264",
|
||||
"plugin-x265",
|
||||
"plugin-ximagesink",
|
||||
@ -72088,13 +72091,22 @@
|
||||
"wpesrc",
|
||||
"wpesrc::configure-web-view",
|
||||
"wpesrc::load-bytes",
|
||||
"wpesrc::run-javascript",
|
||||
"wpesrc:draw-background",
|
||||
"wpesrc:location",
|
||||
"wpevideosrc",
|
||||
"wpevideosrc::configure-web-view",
|
||||
"wpevideosrc::load-bytes",
|
||||
"wpevideosrc::run-javascript",
|
||||
"wpevideosrc:draw-background",
|
||||
"wpevideosrc:location",
|
||||
"wpevideosrc2",
|
||||
"wpevideosrc2::configure-web-view",
|
||||
"wpevideosrc2::load-bytes",
|
||||
"wpevideosrc2::wpe-view-created",
|
||||
"wpevideosrc2::run-javascript",
|
||||
"wpevideosrc2:draw-background",
|
||||
"wpevideosrc2:location",
|
||||
"wrappercamerabinsrc",
|
||||
"wrappercamerabinsrc:video-source",
|
||||
"wrappercamerabinsrc:video-source-filter",
|
||||
|
@ -79,6 +79,7 @@ subdir('webrtcdsp')
|
||||
subdir('webp')
|
||||
subdir('wildmidi')
|
||||
subdir('wpe')
|
||||
subdir('wpe2')
|
||||
subdir('x265')
|
||||
subdir('zxing')
|
||||
subdir('zbar')
|
||||
|
24
subprojects/gst-plugins-bad/ext/wpe2/gstwpe.h
Normal file
24
subprojects/gst-plugins-bad/ext/wpe2/gstwpe.h
Normal file
@ -0,0 +1,24 @@
|
||||
/* Copyright (C) <2018, 2019, 2025> Philippe Normand <philn@igalia.com>
|
||||
* Copyright (C) <2018, 2019> Žan Doberšek <zdobersek@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#define DEFAULT_LOCATION "about:blank"
|
48
subprojects/gst-plugins-bad/ext/wpe2/gstwpe2.cpp
Normal file
48
subprojects/gst-plugins-bad/ext/wpe2/gstwpe2.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
/* Copyright (C) <2018, 2025> Philippe Normand <philn@igalia.com>
|
||||
* Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "gstwpevideosrc.h"
|
||||
#include "gstwpe.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (wpe_video_src_debug);
|
||||
GST_DEBUG_CATEGORY (wpe_view_debug);
|
||||
GST_DEBUG_CATEGORY (wpe_src_debug);
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
gboolean result;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (wpe_video_src_debug, "wpevideosrc2", 0,
|
||||
"WPE Video Source");
|
||||
GST_DEBUG_CATEGORY_INIT (wpe_view_debug, "wpeview2", 0, "WPE Threaded View");
|
||||
GST_DEBUG_CATEGORY_INIT (wpe_src_debug, "wpesrc2", 0, "WPE Source");
|
||||
|
||||
result = gst_element_register (plugin, "wpevideosrc2", GST_RANK_NONE,
|
||||
GST_TYPE_WPE_VIDEO_SRC);
|
||||
return result;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
|
||||
wpe2, "WPE src plugin", plugin_init, VERSION, GST_LICENSE, PACKAGE,
|
||||
GST_PACKAGE_ORIGIN)
|
245
subprojects/gst-plugins-bad/ext/wpe2/gstwpedisplay.cpp
Normal file
245
subprojects/gst-plugins-bad/ext/wpe2/gstwpedisplay.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
/* Copyright (C) <2025> Philippe Normand <philn@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "gstwpedisplay.h"
|
||||
#include "gstwpeview.h"
|
||||
#include "gstwpetoplevel.h"
|
||||
#include <EGL/egl.h>
|
||||
#include <gst/gl/gstglfeature.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (wpe_view_debug);
|
||||
#define GST_CAT_DEFAULT wpe_view_debug
|
||||
|
||||
enum
|
||||
{
|
||||
SIGNAL_WPE_VIEW_CREATED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
static guint gst_wpe_display_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
struct _WPEDisplayGStreamer
|
||||
{
|
||||
WPEDisplay parent;
|
||||
|
||||
GstGLDisplay *gstDisplay;
|
||||
GstGLContext *gstContext;
|
||||
GstGLDisplayEGL *gstEGLDisplay;
|
||||
|
||||
EGLDisplay eglDisplay;
|
||||
gchar *drm_device;
|
||||
gchar *drm_render_node;
|
||||
};
|
||||
|
||||
#define wpe_display_gstreamer_parent_class parent_class
|
||||
G_DEFINE_TYPE (WPEDisplayGStreamer, wpe_display_gstreamer, WPE_TYPE_DISPLAY);
|
||||
|
||||
typedef EGLBoolean (*eglQueryDisplayAttribEXTFunc) (EGLDisplay, EGLint,
|
||||
EGLAttrib *);
|
||||
typedef const char *(*eglQueryDeviceStringEXTFunc) (EGLDeviceEXT device,
|
||||
EGLint name);
|
||||
|
||||
typedef struct _VTable
|
||||
{
|
||||
eglQueryDisplayAttribEXTFunc eglQueryDisplayAttribEXT;
|
||||
eglQueryDeviceStringEXTFunc eglQueryDeviceStringEXT;
|
||||
} VTable;
|
||||
|
||||
static gboolean
|
||||
wpe_display_gstreamer_connect (WPEDisplay * display, GError ** error)
|
||||
{
|
||||
auto self = WPE_DISPLAY_GSTREAMER (display);
|
||||
|
||||
if (!self->gstDisplay)
|
||||
return TRUE;
|
||||
|
||||
if (gst_gl_context_get_gl_platform (self->gstContext) == GST_GL_PLATFORM_EGL) {
|
||||
self->gstEGLDisplay = gst_gl_display_egl_from_gl_display (self->gstDisplay);
|
||||
} else {
|
||||
g_set_error_literal (error, WPE_VIEW_ERROR, WPE_VIEW_ERROR_RENDER_FAILED,
|
||||
"Available GStreamer GL Context is not EGL - not creating an EGL display from it");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
const gchar *egl_exts = eglQueryString (EGL_NO_DISPLAY, EGL_EXTENSIONS);
|
||||
|
||||
self->eglDisplay = (EGLDisplay)
|
||||
gst_gl_display_get_handle (GST_GL_DISPLAY (self->gstEGLDisplay));
|
||||
|
||||
if (!gst_gl_check_extension ("EGL_EXT_device_query", egl_exts)) {
|
||||
g_set_error_literal (error, WPE_VIEW_ERROR, WPE_VIEW_ERROR_RENDER_FAILED,
|
||||
"Failed to initialize rendering: 'EGL_EXT_device_query' not available");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
EGLDeviceEXT eglDevice;
|
||||
VTable vt;
|
||||
vt.eglQueryDisplayAttribEXT = (eglQueryDisplayAttribEXTFunc)
|
||||
gst_gl_context_get_proc_address (self->gstContext,
|
||||
"eglQueryDisplayAttribEXT");
|
||||
if (!vt.eglQueryDisplayAttribEXT (self->eglDisplay, EGL_DEVICE_EXT,
|
||||
reinterpret_cast < EGLAttrib * >(&eglDevice))) {
|
||||
g_set_error_literal (error, WPE_VIEW_ERROR, WPE_VIEW_ERROR_RENDER_FAILED,
|
||||
"Failed to initialize rendering: 'EGLDeviceEXT' not available");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
vt.eglQueryDeviceStringEXT = (eglQueryDeviceStringEXTFunc)
|
||||
gst_gl_context_get_proc_address (self->gstContext,
|
||||
"eglQueryDeviceStringEXT");
|
||||
|
||||
const char *extensions =
|
||||
vt.eglQueryDeviceStringEXT (eglDevice, EGL_EXTENSIONS);
|
||||
if (gst_gl_check_extension ("EGL_EXT_device_drm", extensions))
|
||||
self->drm_device =
|
||||
g_strdup (vt.eglQueryDeviceStringEXT (eglDevice,
|
||||
EGL_DRM_DEVICE_FILE_EXT));
|
||||
else {
|
||||
// FIXME: This kind of hack is needed when using gtkglsink. glimagesink somehow works as expected.
|
||||
const gchar *render_node_path = g_getenv ("GST_WPE_DRM_RENDER_NODE_PATH");
|
||||
if (render_node_path) {
|
||||
GST_DEBUG ("Setting render node path from GST_WPE_DRM_RENDER_NODE_PATH "
|
||||
"environment variable");
|
||||
self->drm_render_node = g_strdup (render_node_path);
|
||||
} else {
|
||||
GST_WARNING ("'EGL_EXT_device_drm' not available, hardcoding render node "
|
||||
"to /dev/dri/renderD128");
|
||||
self->drm_render_node = g_strdup ("/dev/dri/renderD128");
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (gst_gl_check_extension ("EGL_EXT_device_drm_render_node", extensions))
|
||||
self->drm_render_node =
|
||||
g_strdup (vt.eglQueryDeviceStringEXT (eglDevice,
|
||||
EGL_DRM_RENDER_NODE_FILE_EXT));
|
||||
else {
|
||||
g_set_error_literal (error, WPE_VIEW_ERROR, WPE_VIEW_ERROR_RENDER_FAILED,
|
||||
"Failed to initialize rendering: 'EGL_EXT_device_drm_render_node' not available");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static WPEView *
|
||||
wpe_display_gstreamer_create_view (WPEDisplay * display)
|
||||
{
|
||||
auto gst_display = WPE_DISPLAY_GSTREAMER (display);
|
||||
auto view = wpe_view_gstreamer_new (gst_display);
|
||||
GValue args[2] = { {0}, {0} };
|
||||
|
||||
g_value_init (&args[0], WPE_TYPE_DISPLAY_GSTREAMER);
|
||||
g_value_set_object (&args[0], gst_display);
|
||||
|
||||
g_value_init (&args[1], WPE_TYPE_VIEW);
|
||||
g_value_set_object (&args[1], view);
|
||||
|
||||
g_signal_emitv (args, gst_wpe_display_signals[SIGNAL_WPE_VIEW_CREATED], 0,
|
||||
NULL);
|
||||
|
||||
g_value_unset (&args[0]);
|
||||
g_value_unset (&args[1]);
|
||||
|
||||
auto toplevel = wpe_toplevel_gstreamer_new (gst_display);
|
||||
wpe_view_set_toplevel (view, toplevel);
|
||||
g_object_unref (toplevel);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
wpe_display_gstreamer_get_egl_display (WPEDisplay * display, GError **)
|
||||
{
|
||||
return WPE_DISPLAY_GSTREAMER (display)->eglDisplay;
|
||||
}
|
||||
|
||||
static const char *
|
||||
wpe_display_gstreamer_get_drm_device (WPEDisplay * display)
|
||||
{
|
||||
return WPE_DISPLAY_GSTREAMER (display)->drm_device;
|
||||
}
|
||||
|
||||
static const char *
|
||||
wpe_display_gstreamer_get_drm_render_node (WPEDisplay * display)
|
||||
{
|
||||
auto self = WPE_DISPLAY_GSTREAMER (display);
|
||||
if (self->drm_render_node)
|
||||
return self->drm_render_node;
|
||||
return self->drm_device;
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_display_gstreamer_init (WPEDisplayGStreamer * display)
|
||||
{
|
||||
display->drm_render_node = nullptr;
|
||||
display->drm_device = nullptr;
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_display_gstreamer_finalize (GObject * object)
|
||||
{
|
||||
auto self = WPE_DISPLAY_GSTREAMER (object);
|
||||
|
||||
g_clear_pointer (&self->drm_device, g_free);
|
||||
g_clear_pointer (&self->drm_render_node, g_free);
|
||||
|
||||
gst_clear_object (&self->gstEGLDisplay);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_display_gstreamer_class_init (WPEDisplayGStreamerClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
gobject_class->finalize = wpe_display_gstreamer_finalize;
|
||||
|
||||
WPEDisplayClass *displayClass = WPE_DISPLAY_CLASS (klass);
|
||||
displayClass->connect = wpe_display_gstreamer_connect;
|
||||
displayClass->create_view = wpe_display_gstreamer_create_view;
|
||||
displayClass->get_egl_display = wpe_display_gstreamer_get_egl_display;
|
||||
displayClass->get_drm_device = wpe_display_gstreamer_get_drm_device;
|
||||
displayClass->get_drm_render_node = wpe_display_gstreamer_get_drm_render_node;
|
||||
|
||||
gst_wpe_display_signals[SIGNAL_WPE_VIEW_CREATED] =
|
||||
g_signal_new ("wpe-view-created", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, WPE_TYPE_VIEW);
|
||||
}
|
||||
|
||||
WPEDisplay *
|
||||
wpe_display_gstreamer_new ()
|
||||
{
|
||||
auto display =
|
||||
WPE_DISPLAY_GSTREAMER (g_object_new (WPE_TYPE_DISPLAY_GSTREAMER,
|
||||
nullptr));
|
||||
return WPE_DISPLAY (display);
|
||||
}
|
||||
|
||||
void
|
||||
wpe_display_gstreamer_set_gl (WPEDisplay * display, GstGLDisplay * glDisplay,
|
||||
GstGLContext * context)
|
||||
{
|
||||
auto self = WPE_DISPLAY_GSTREAMER (display);
|
||||
self->gstDisplay = glDisplay;
|
||||
self->gstContext = context;
|
||||
}
|
42
subprojects/gst-plugins-bad/ext/wpe2/gstwpedisplay.h
Normal file
42
subprojects/gst-plugins-bad/ext/wpe2/gstwpedisplay.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) <2025> Philippe Normand <philn@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef GstWPEDisplay_h
|
||||
#define GstWPEDisplay_h
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <wpe/wpe-platform.h>
|
||||
|
||||
#include <gst/gl/egl/gstgldisplay_egl.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
#include "gstwpevideosrc.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define WPE_TYPE_DISPLAY_GSTREAMER (wpe_display_gstreamer_get_type())
|
||||
G_DECLARE_FINAL_TYPE(WPEDisplayGStreamer, wpe_display_gstreamer, WPE,
|
||||
DISPLAY_GSTREAMER, WPEDisplay)
|
||||
|
||||
WPEDisplay *wpe_display_gstreamer_new();
|
||||
|
||||
void wpe_display_gstreamer_set_gl(WPEDisplay *, GstGLDisplay *, GstGLContext *);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* GstWPEDisplay_h */
|
946
subprojects/gst-plugins-bad/ext/wpe2/gstwpethreadedview.cpp
Normal file
946
subprojects/gst-plugins-bad/ext/wpe2/gstwpethreadedview.cpp
Normal file
@ -0,0 +1,946 @@
|
||||
/* Copyright (C) <2018, 2019, 2020, 2025> Philippe Normand <philn@igalia.com>
|
||||
* Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "gstwpe.h"
|
||||
#include "gstwpethreadedview.h"
|
||||
#include "gstwpedisplay.h"
|
||||
#include "gstwpeview.h"
|
||||
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/egl/gsteglimage.h>
|
||||
#include <gst/gl/egl/gstgldisplay_egl.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (wpe_view_debug);
|
||||
#define GST_CAT_DEFAULT wpe_view_debug
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
class GMutexHolder {
|
||||
public:
|
||||
GMutexHolder (GMutex & mutex)
|
||||
:m(mutex)
|
||||
{
|
||||
g_mutex_lock (&m);
|
||||
}
|
||||
~GMutexHolder ()
|
||||
{
|
||||
g_mutex_unlock (&m);
|
||||
}
|
||||
|
||||
private:
|
||||
GMutex &m;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
static GstWPEContextThread *s_view = NULL;
|
||||
|
||||
GstWPEContextThread & GstWPEContextThread::singleton ()
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
static gsize initialized = 0;
|
||||
/* *INDENT-ON* */
|
||||
|
||||
if (g_once_init_enter (&initialized)) {
|
||||
s_view = new GstWPEContextThread;
|
||||
|
||||
g_once_init_leave (&initialized, 1);
|
||||
}
|
||||
|
||||
return *s_view;
|
||||
}
|
||||
|
||||
GstWPEContextThread::GstWPEContextThread ()
|
||||
{
|
||||
g_mutex_init (&threading.mutex);
|
||||
g_cond_init (&threading.cond);
|
||||
threading.ready = FALSE;
|
||||
|
||||
{
|
||||
GMutexHolder lock (threading.mutex);
|
||||
threading.thread = g_thread_new ("GstWPEContextThread", s_viewThread, this);
|
||||
while (!threading.ready) {
|
||||
g_cond_wait (&threading.cond, &threading.mutex);
|
||||
}
|
||||
GST_DEBUG ("thread spawned");
|
||||
}
|
||||
}
|
||||
|
||||
GstWPEContextThread::~GstWPEContextThread ()
|
||||
{
|
||||
if (threading.thread) {
|
||||
g_thread_unref (threading.thread);
|
||||
threading.thread = nullptr;
|
||||
}
|
||||
|
||||
g_mutex_clear (&threading.mutex);
|
||||
g_cond_clear (&threading.cond);
|
||||
}
|
||||
|
||||
template < typename Function > void
|
||||
GstWPEContextThread::dispatch (Function func)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
struct Job {
|
||||
Job (Function & f)
|
||||
:func (f)
|
||||
{
|
||||
g_mutex_init (&mutex);
|
||||
g_cond_init (&cond);
|
||||
dispatched = FALSE;
|
||||
}
|
||||
~Job ()
|
||||
{
|
||||
g_mutex_clear (&mutex);
|
||||
g_cond_clear (&cond);
|
||||
}
|
||||
|
||||
void dispatch ()
|
||||
{
|
||||
GMutexHolder lock (mutex);
|
||||
func ();
|
||||
dispatched = TRUE;
|
||||
g_cond_signal (&cond);
|
||||
}
|
||||
|
||||
void waitCompletion ()
|
||||
{
|
||||
GMutexHolder lock (mutex);
|
||||
while (!dispatched) {
|
||||
g_cond_wait (&cond, &mutex);
|
||||
}
|
||||
}
|
||||
|
||||
Function & func;
|
||||
GMutex mutex;
|
||||
GCond cond;
|
||||
gboolean dispatched;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
struct Job job (func);
|
||||
GSource *source = g_idle_source_new ();
|
||||
/* *INDENT-OFF* */
|
||||
g_source_set_callback (source,[](gpointer data)->gboolean {
|
||||
auto job = static_cast<struct Job *>(data);
|
||||
job->dispatch ();
|
||||
return G_SOURCE_REMOVE;
|
||||
}, &job, nullptr);
|
||||
/* *INDENT-ON* */
|
||||
g_source_set_priority (source, G_PRIORITY_DEFAULT);
|
||||
g_source_attach (source, glib.context);
|
||||
job.waitCompletion ();
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
gpointer
|
||||
GstWPEContextThread::s_viewThread (gpointer data)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
auto &view = *static_cast<GstWPEContextThread *>(data);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
view.glib.context = g_main_context_new ();
|
||||
view.glib.loop = g_main_loop_new (view.glib.context, FALSE);
|
||||
|
||||
g_main_context_push_thread_default (view.glib.context);
|
||||
|
||||
{
|
||||
GSource *source = g_idle_source_new ();
|
||||
/* *INDENT-OFF* */
|
||||
g_source_set_callback(source, [](gpointer data) -> gboolean {
|
||||
auto& view = *static_cast<GstWPEContextThread*>(data);
|
||||
GMutexHolder lock (view.threading.mutex);
|
||||
view.threading.ready = TRUE;
|
||||
g_cond_signal(&view.threading.cond);
|
||||
return G_SOURCE_REMOVE;
|
||||
}, &view, nullptr);
|
||||
/* *INDENT-ON* */
|
||||
g_source_attach (source, view.glib.context);
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
g_main_loop_run (view.glib.loop);
|
||||
|
||||
g_main_loop_unref (view.glib.loop);
|
||||
view.glib.loop = nullptr;
|
||||
|
||||
g_main_context_pop_thread_default (view.glib.context);
|
||||
g_main_context_unref (view.glib.context);
|
||||
view.glib.context = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GstWPEThreadedView *
|
||||
GstWPEContextThread::createWPEView (GstWpeVideoSrc2 * src,
|
||||
GstGLContext * context,
|
||||
GstGLDisplay * display, WPEDisplay * wpe_display, int width, int height)
|
||||
{
|
||||
GST_DEBUG ("context %p display %p, size (%d,%d)", context, display, width,
|
||||
height);
|
||||
|
||||
GstWPEThreadedView *view = nullptr;
|
||||
/* *INDENT-OFF* */
|
||||
dispatch([&]() mutable {
|
||||
if (!glib.web_context) {
|
||||
glib.web_context =
|
||||
WEBKIT_WEB_CONTEXT (g_object_new (WEBKIT_TYPE_WEB_CONTEXT, nullptr));
|
||||
}
|
||||
view =
|
||||
new GstWPEThreadedView (glib.web_context, src, context, display, wpe_display,
|
||||
width, height);
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
|
||||
if (view && view->hasUri ()) {
|
||||
GST_DEBUG ("waiting load to finish");
|
||||
view->waitLoadCompletion ();
|
||||
GST_DEBUG ("done");
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
s_loadFailed (WebKitWebView *, WebKitLoadEvent, gchar * failing_uri,
|
||||
GError * error, gpointer data)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (data);
|
||||
|
||||
if (g_error_matches (error, WEBKIT_NETWORK_ERROR,
|
||||
WEBKIT_NETWORK_ERROR_CANCELLED)) {
|
||||
GST_INFO_OBJECT (src, "Loading cancelled.");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_ELEMENT_ERROR (GST_ELEMENT_CAST (src), RESOURCE, FAILED, (NULL),
|
||||
("Failed to load %s (%s)", failing_uri, error->message));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
s_loadFailedWithTLSErrors (WebKitWebView *, gchar * failing_uri,
|
||||
GTlsCertificate *, GTlsCertificateFlags, gpointer data)
|
||||
{
|
||||
// Defer to load-failed.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
s_loadProgressChanged (GObject * object, GParamSpec *, gpointer data)
|
||||
{
|
||||
GstElement *src = GST_ELEMENT_CAST (data);
|
||||
// The src element is locked already so we can't call
|
||||
// gst_element_post_message(). Instead retrieve the bus manually and use it
|
||||
// directly.
|
||||
GstBus *bus = GST_ELEMENT_BUS (src);
|
||||
double estimatedProgress;
|
||||
g_object_get (object, "estimated-load-progress", &estimatedProgress, nullptr);
|
||||
gst_object_ref (bus);
|
||||
gst_bus_post (bus, gst_message_new_element (GST_OBJECT_CAST (src),
|
||||
gst_structure_new ("wpe-stats", "estimated-load-progress",
|
||||
G_TYPE_DOUBLE, estimatedProgress * 100, nullptr)));
|
||||
gst_object_unref (bus);
|
||||
}
|
||||
|
||||
static void
|
||||
s_webProcessCrashed (WebKitWebView *, WebKitWebProcessTerminationReason reason,
|
||||
gpointer data)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
auto &view = *static_cast<GstWPEThreadedView *>(data);
|
||||
/* *INDENT-ON* */
|
||||
auto *src = view.src ();
|
||||
gchar *reason_str =
|
||||
g_enum_to_string (WEBKIT_TYPE_WEB_PROCESS_TERMINATION_REASON, reason);
|
||||
|
||||
// In case the crash happened while doing the initial URL loading, unlock
|
||||
// the load completion waiting.
|
||||
view.notifyLoadFinished ();
|
||||
|
||||
// TODO: Emit a signal here and fallback to error system if signal wasn't handled by application?
|
||||
|
||||
GST_ELEMENT_ERROR (GST_ELEMENT_CAST (src), RESOURCE, FAILED, (NULL), ("%s",
|
||||
reason_str));
|
||||
|
||||
g_free (reason_str);
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
GstWPEThreadedView::GstWPEThreadedView(
|
||||
WebKitWebContext *web_context, GstWpeVideoSrc2 *src, GstGLContext *context,
|
||||
GstGLDisplay *display, WPEDisplay *wpe_display, int width, int height)
|
||||
: m_src(src) {
|
||||
g_mutex_init (&threading.ready_mutex);
|
||||
g_cond_init (&threading.ready_cond);
|
||||
threading.ready = FALSE;
|
||||
|
||||
g_mutex_init (&images_mutex);
|
||||
if (context)
|
||||
gst.context = GST_GL_CONTEXT (gst_object_ref (context));
|
||||
if (display)
|
||||
gst.display = GST_GL_DISPLAY (gst_object_ref (display));
|
||||
|
||||
wpe.width = width;
|
||||
wpe.height = height;
|
||||
|
||||
auto *defaultWebsitePolicies = webkit_website_policies_new_with_policies(
|
||||
"autoplay", WEBKIT_AUTOPLAY_ALLOW, nullptr);
|
||||
|
||||
webkit.view = WEBKIT_WEB_VIEW(g_object_new(
|
||||
WEBKIT_TYPE_WEB_VIEW, "web-context", web_context, "display", wpe_display,
|
||||
"website-policies", defaultWebsitePolicies, nullptr));
|
||||
|
||||
g_object_unref (wpe_display);
|
||||
g_object_unref(defaultWebsitePolicies);
|
||||
|
||||
wpe.view = webkit_web_view_get_wpe_view (webkit.view);
|
||||
wpe_view_gstreamer_set_client (WPE_VIEW_GSTREAMER (wpe.view), this);
|
||||
if (auto wpeToplevel = wpe_view_get_toplevel (wpe.view))
|
||||
wpe_toplevel_resize (wpeToplevel, width, height);
|
||||
|
||||
// FIXME: unmap when appropriate and implement can_be_mapped if needed.
|
||||
wpe_view_map (wpe.view);
|
||||
|
||||
g_signal_connect (webkit.view, "load-failed", G_CALLBACK (s_loadFailed), src);
|
||||
g_signal_connect (webkit.view, "load-failed-with-tls-errors",
|
||||
G_CALLBACK (s_loadFailedWithTLSErrors), src);
|
||||
g_signal_connect (webkit.view, "notify::estimated-load-progress",
|
||||
G_CALLBACK (s_loadProgressChanged), src);
|
||||
g_signal_connect (webkit.view, "web-process-terminated",
|
||||
G_CALLBACK (s_webProcessCrashed), this);
|
||||
|
||||
auto *settings = webkit_web_view_get_settings (webkit.view);
|
||||
webkit_settings_set_enable_webaudio (settings, TRUE);
|
||||
|
||||
gst_wpe_video_src_configure_web_view (src, webkit.view);
|
||||
|
||||
gchar *location;
|
||||
gboolean drawBackground = TRUE;
|
||||
g_object_get (src, "location", &location, "draw-background", &drawBackground, nullptr);
|
||||
setDrawBackground (drawBackground);
|
||||
if (location) {
|
||||
loadUriUnlocked (location);
|
||||
g_free (location);
|
||||
}
|
||||
}
|
||||
/* *INDENT-ON* */
|
||||
|
||||
GstWPEThreadedView::~GstWPEThreadedView ()
|
||||
{
|
||||
GstEGLImage *egl_pending = NULL;
|
||||
GstEGLImage *egl_committed = NULL;
|
||||
GstBuffer *shm_pending = NULL;
|
||||
GstBuffer *shm_committed = NULL;
|
||||
GST_TRACE ("%p destroying", this);
|
||||
|
||||
g_mutex_clear (&threading.ready_mutex);
|
||||
g_cond_clear (&threading.ready_cond);
|
||||
|
||||
{
|
||||
GMutexHolder lock (images_mutex);
|
||||
|
||||
if (egl.pending) {
|
||||
egl_pending = egl.pending;
|
||||
egl.pending = nullptr;
|
||||
}
|
||||
if (egl.committed) {
|
||||
egl_committed = egl.committed;
|
||||
egl.committed = nullptr;
|
||||
}
|
||||
if (shm.pending) {
|
||||
GST_TRACE ("%p freeing shm pending %" GST_PTR_FORMAT, this, shm.pending);
|
||||
shm_pending = shm.pending;
|
||||
shm.pending = nullptr;
|
||||
}
|
||||
if (shm.committed) {
|
||||
GST_TRACE ("%p freeing shm commited %" GST_PTR_FORMAT, this,
|
||||
shm.committed);
|
||||
shm_committed = shm.committed;
|
||||
shm.committed = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (egl_pending)
|
||||
gst_egl_image_unref (egl_pending);
|
||||
if (egl_committed)
|
||||
gst_egl_image_unref (egl_committed);
|
||||
if (shm_pending)
|
||||
gst_buffer_unref (shm_pending);
|
||||
if (shm_committed)
|
||||
gst_buffer_unref (shm_committed);
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
GstWPEContextThread::singleton().dispatch([&]() {
|
||||
if (webkit.view) {
|
||||
g_object_unref (webkit.view);
|
||||
webkit.view = nullptr;
|
||||
}
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
|
||||
if (gst.display_egl) {
|
||||
gst_object_unref (gst.display_egl);
|
||||
gst.display_egl = nullptr;
|
||||
}
|
||||
|
||||
if (gst.display) {
|
||||
gst_object_unref (gst.display);
|
||||
gst.display = nullptr;
|
||||
}
|
||||
|
||||
if (gst.context) {
|
||||
gst_object_unref (gst.context);
|
||||
gst.context = nullptr;
|
||||
}
|
||||
if (webkit.uri) {
|
||||
g_free (webkit.uri);
|
||||
webkit.uri = nullptr;
|
||||
}
|
||||
|
||||
g_mutex_clear (&images_mutex);
|
||||
GST_TRACE ("%p destroyed", this);
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::notifyLoadFinished ()
|
||||
{
|
||||
GMutexHolder lock (threading.ready_mutex);
|
||||
if (!threading.ready) {
|
||||
threading.ready = TRUE;
|
||||
g_cond_signal (&threading.ready_cond);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::waitLoadCompletion ()
|
||||
{
|
||||
GMutexHolder lock (threading.ready_mutex);
|
||||
while (!threading.ready)
|
||||
g_cond_wait (&threading.ready_cond, &threading.ready_mutex);
|
||||
}
|
||||
|
||||
GstEGLImage *
|
||||
GstWPEThreadedView::image ()
|
||||
{
|
||||
GstEGLImage *ret = nullptr;
|
||||
bool dispatchFrameComplete = false;
|
||||
GstEGLImage *prev_image = NULL;
|
||||
|
||||
{
|
||||
GMutexHolder lock (images_mutex);
|
||||
|
||||
GST_TRACE ("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT
|
||||
" (%d)", egl.pending,
|
||||
GST_IS_EGL_IMAGE (egl.pending) ?
|
||||
GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (egl.pending)) : 0,
|
||||
egl.committed,
|
||||
GST_IS_EGL_IMAGE (egl.committed) ?
|
||||
GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (egl.committed)) :
|
||||
0);
|
||||
|
||||
if (egl.pending) {
|
||||
prev_image = egl.committed;
|
||||
egl.committed = egl.pending;
|
||||
egl.pending = nullptr;
|
||||
|
||||
dispatchFrameComplete = true;
|
||||
}
|
||||
|
||||
if (egl.committed)
|
||||
ret = egl.committed;
|
||||
}
|
||||
|
||||
if (prev_image) {
|
||||
gst_egl_image_unref (prev_image);
|
||||
}
|
||||
|
||||
if (dispatchFrameComplete) {
|
||||
frameComplete ();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
GstBuffer *
|
||||
GstWPEThreadedView::buffer ()
|
||||
{
|
||||
GstBuffer *ret = nullptr;
|
||||
bool dispatchFrameComplete = false;
|
||||
GstBuffer *prev_image = NULL;
|
||||
|
||||
{
|
||||
GMutexHolder lock (images_mutex);
|
||||
|
||||
GST_TRACE ("pending %" GST_PTR_FORMAT " (%d) committed %" GST_PTR_FORMAT
|
||||
" (%d)", shm.pending,
|
||||
GST_IS_BUFFER (shm.pending) ?
|
||||
GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (shm.pending)) : 0,
|
||||
shm.committed,
|
||||
GST_IS_BUFFER (shm.committed) ?
|
||||
GST_MINI_OBJECT_REFCOUNT_VALUE (GST_MINI_OBJECT_CAST (shm.committed)) :
|
||||
0);
|
||||
|
||||
if (shm.pending) {
|
||||
prev_image = shm.committed;
|
||||
shm.committed = shm.pending;
|
||||
shm.pending = nullptr;
|
||||
|
||||
dispatchFrameComplete = true;
|
||||
}
|
||||
|
||||
if (shm.committed)
|
||||
ret = shm.committed;
|
||||
}
|
||||
|
||||
if (prev_image)
|
||||
gst_buffer_unref (prev_image);
|
||||
|
||||
if (dispatchFrameComplete) {
|
||||
frameComplete ();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::resize (int width, int height)
|
||||
{
|
||||
GST_DEBUG ("resize to %dx%d", width, height);
|
||||
wpe.width = width;
|
||||
wpe.height = height;
|
||||
if (auto wpeToplevel = wpe_view_get_toplevel (wpe.view))
|
||||
wpe_toplevel_resize (wpeToplevel, wpe.width, wpe.height);
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::clearBuffers ()
|
||||
{
|
||||
bool dispatchFrameComplete = false;
|
||||
{
|
||||
GMutexHolder lock (images_mutex);
|
||||
|
||||
if (shm.pending) {
|
||||
auto meta = gst_buffer_get_video_meta (shm.pending);
|
||||
if (static_cast < int >(meta->width) != wpe.width ||
|
||||
static_cast < int >(meta->height) != wpe.height) {
|
||||
gst_clear_buffer (&shm.pending);
|
||||
dispatchFrameComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shm.committed) {
|
||||
auto meta = gst_buffer_get_video_meta (shm.committed);
|
||||
if (static_cast < int >(meta->width) != wpe.width ||
|
||||
static_cast < int >(meta->height) != wpe.height) {
|
||||
gst_clear_buffer (&shm.committed);
|
||||
dispatchFrameComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dispatchFrameComplete) {
|
||||
frameComplete ();
|
||||
// Wait until the next SHM buffer has been received.
|
||||
threading.ready = false;
|
||||
waitLoadCompletion ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::loadUriUnlocked (const gchar * uri)
|
||||
{
|
||||
if (webkit.uri)
|
||||
g_free (webkit.uri);
|
||||
|
||||
GST_DEBUG ("loading %s", uri);
|
||||
webkit.uri = g_strdup (uri);
|
||||
webkit_web_view_load_uri (webkit.view, webkit.uri);
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::loadUri (const gchar * uri)
|
||||
{
|
||||
s_view->dispatch ([&]() {
|
||||
loadUriUnlocked (uri);});
|
||||
}
|
||||
|
||||
static void
|
||||
s_runJavascriptFinished (GObject * object, GAsyncResult * result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
g_autoptr (JSCValue) js_result =
|
||||
webkit_web_view_evaluate_javascript_finish (WEBKIT_WEB_VIEW (object),
|
||||
result, &error);
|
||||
|
||||
// TODO: Pass result back to signal call site using a GstPromise?
|
||||
(void) js_result;
|
||||
|
||||
if (error) {
|
||||
GST_WARNING ("Error running javascript: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::runJavascript (const char *script)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
s_view->dispatch([&]() {
|
||||
webkit_web_view_evaluate_javascript(webkit.view, script, -1, nullptr,
|
||||
nullptr, nullptr,
|
||||
s_runJavascriptFinished, nullptr);
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::loadData (GBytes * bytes)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
s_view->dispatch([this, bytes = g_bytes_ref(bytes)]() {
|
||||
webkit_web_view_load_bytes(webkit.view, bytes, nullptr, nullptr, nullptr);
|
||||
g_bytes_unref(bytes);
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::setDrawBackground (gboolean drawsBackground)
|
||||
{
|
||||
GST_DEBUG ("%s background rendering",
|
||||
drawsBackground ? "Enabling" : "Disabling");
|
||||
WebKitColor color;
|
||||
webkit_color_parse (&color, drawsBackground ? "white" : "transparent");
|
||||
webkit_web_view_set_background_color (webkit.view, &color);
|
||||
}
|
||||
|
||||
struct WPEBufferContext
|
||||
{
|
||||
GstWPEThreadedView *view;
|
||||
WPEBuffer *buffer;
|
||||
};
|
||||
|
||||
void
|
||||
GstWPEThreadedView::s_releaseBuffer (gpointer data)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
s_view->dispatch([&]() {
|
||||
WPEBufferContext *context = static_cast<WPEBufferContext *>(data);
|
||||
wpe_view_buffer_released(WPE_VIEW(context->view->wpe.view),
|
||||
context->buffer);
|
||||
g_object_unref(context->buffer);
|
||||
g_free(context);
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
gboolean GstWPEThreadedView::setPendingBuffer(WPEBuffer *buffer, GError **error)
|
||||
{
|
||||
WPEBufferContext *bufferContext = g_new (WPEBufferContext, 1);
|
||||
bufferContext->view = this;
|
||||
bufferContext->buffer = g_object_ref (buffer);
|
||||
|
||||
if (WPE_IS_BUFFER_DMA_BUF (buffer)) {
|
||||
auto eglImage = wpe_buffer_import_to_egl_image (buffer, error);
|
||||
if (*error)
|
||||
return FALSE;
|
||||
|
||||
auto *gstImage =
|
||||
gst_egl_image_new_wrapped (gst.context, eglImage, GST_GL_RGBA,
|
||||
bufferContext,[](GstEGLImage *, gpointer data) { s_releaseBuffer (data); });
|
||||
{
|
||||
GMutexHolder lock (images_mutex);
|
||||
|
||||
GST_TRACE ("EGLImage %p wrapped in GstEGLImage %" GST_PTR_FORMAT,
|
||||
eglImage, gstImage);
|
||||
gst_clear_mini_object ((GstMiniObject **) & egl.pending);
|
||||
egl.pending = gstImage;
|
||||
|
||||
m_pending_buffer = g_object_ref (buffer);
|
||||
notifyLoadFinished ();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!WPE_IS_BUFFER_SHM (buffer)) {
|
||||
g_set_error_literal (error, WPE_VIEW_ERROR, WPE_VIEW_ERROR_RENDER_FAILED,
|
||||
"Unsupported WPEBuffer format");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GBytes *bytes = wpe_buffer_import_to_pixels (buffer, error);
|
||||
if (!bytes) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto width = wpe_buffer_get_width (buffer);
|
||||
auto height = wpe_buffer_get_height (buffer);
|
||||
|
||||
guint stride;
|
||||
g_object_get (buffer, "stride", &stride, nullptr);
|
||||
|
||||
gsize size = g_bytes_get_size (bytes);
|
||||
auto *gstBuffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
|
||||
(gpointer) g_bytes_get_data (bytes, nullptr), size, 0, size,
|
||||
bufferContext, s_releaseBuffer);
|
||||
gsize offsets[1];
|
||||
gint strides[1];
|
||||
offsets[0] = 0;
|
||||
strides[0] = stride;
|
||||
gst_buffer_add_video_meta_full (gstBuffer, GST_VIDEO_FRAME_FLAG_NONE,
|
||||
GST_VIDEO_FORMAT_BGRA, width, height, 1, offsets, strides);
|
||||
|
||||
{
|
||||
GMutexHolder lock (images_mutex);
|
||||
GST_TRACE ("SHM buffer %p wrapped in buffer %" GST_PTR_FORMAT, buffer,
|
||||
gstBuffer);
|
||||
gst_clear_buffer (&shm.pending);
|
||||
shm.pending = gstBuffer;
|
||||
m_pending_buffer = g_object_ref (buffer);
|
||||
notifyLoadFinished ();
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
/* *INDENT-ON* */
|
||||
|
||||
static uint32_t
|
||||
_pointer_modifiers_from_gst_event (GstEvent * ev)
|
||||
{
|
||||
GstNavigationModifierType modifier_state;
|
||||
uint32_t modifiers = 0;
|
||||
|
||||
if (gst_navigation_event_parse_modifier_state (ev, &modifier_state)) {
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK)
|
||||
modifiers |= WPE_MODIFIER_POINTER_BUTTON1;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK)
|
||||
modifiers |= WPE_MODIFIER_POINTER_BUTTON2;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON3_MASK)
|
||||
modifiers |= WPE_MODIFIER_POINTER_BUTTON3;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON4_MASK)
|
||||
modifiers |= WPE_MODIFIER_POINTER_BUTTON4;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON5_MASK)
|
||||
modifiers |= WPE_MODIFIER_POINTER_BUTTON5;
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
_keyboard_modifiers_from_gst_event (GstEvent * ev)
|
||||
{
|
||||
GstNavigationModifierType modifier_state;
|
||||
uint32_t modifiers = 0;
|
||||
|
||||
if (gst_navigation_event_parse_modifier_state (ev, &modifier_state)) {
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_CONTROL_MASK)
|
||||
modifiers |= WPE_MODIFIER_KEYBOARD_CONTROL;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_SHIFT_MASK)
|
||||
modifiers |= WPE_MODIFIER_KEYBOARD_SHIFT;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_MOD1_MASK)
|
||||
modifiers |= WPE_MODIFIER_KEYBOARD_ALT;
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_META_MASK)
|
||||
modifiers |= WPE_MODIFIER_KEYBOARD_META;
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
static WPEModifiers
|
||||
modifiers_from_gst_event (GstEvent * event)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
return static_cast<WPEModifiers>
|
||||
(_pointer_modifiers_from_gst_event (event) |
|
||||
_keyboard_modifiers_from_gst_event (event));
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::frameComplete ()
|
||||
{
|
||||
GST_TRACE ("frame complete");
|
||||
/* *INDENT-OFF* */
|
||||
s_view->dispatch([&]() {
|
||||
if (m_committed_buffer) {
|
||||
wpe_view_buffer_released(WPE_VIEW(wpe.view), m_committed_buffer);
|
||||
g_object_unref(m_committed_buffer);
|
||||
}
|
||||
m_committed_buffer = m_pending_buffer;
|
||||
wpe_view_buffer_rendered (WPE_VIEW (wpe.view), m_committed_buffer);
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
void
|
||||
GstWPEThreadedView::dispatchEvent (WPEEvent * wpe_event)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
s_view->dispatch([&]() {
|
||||
wpe_view_event(WPE_VIEW(wpe.view), wpe_event);
|
||||
wpe_event_unref(wpe_event);
|
||||
});
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
gboolean GstWPEThreadedView::dispatchKeyboardEvent(GstEvent *event) {
|
||||
const gchar *key;
|
||||
if (!gst_navigation_event_parse_key_event (event, &key)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto modifiers = static_cast<WPEModifiers>(_keyboard_modifiers_from_gst_event (event));
|
||||
auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
|
||||
|
||||
/* FIXME: This is wrong... The GstNavigation API should pass
|
||||
hardware-level information, not high-level keysym strings */
|
||||
gunichar *unichar;
|
||||
glong items_written;
|
||||
uint32_t keysym;
|
||||
|
||||
unichar = g_utf8_to_ucs4_fast (key, -1, &items_written);
|
||||
if (items_written == 1)
|
||||
keysym = (uint32_t) xkb_utf32_to_keysym (*unichar);
|
||||
else
|
||||
keysym = (uint32_t) xkb_keysym_from_name (key, XKB_KEYSYM_NO_FLAGS);
|
||||
|
||||
WPEEventType event_type = WPE_EVENT_NONE;
|
||||
if (gst_navigation_event_get_type (event) == GST_NAVIGATION_EVENT_KEY_PRESS)
|
||||
event_type = WPE_EVENT_KEYBOARD_KEY_DOWN;
|
||||
else
|
||||
event_type = WPE_EVENT_KEYBOARD_KEY_UP;
|
||||
|
||||
dispatchEvent (wpe_event_keyboard_new (event_type, WPE_VIEW (wpe.view),
|
||||
WPE_INPUT_SOURCE_KEYBOARD, timestamp, modifiers, keysym, keysym));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean GstWPEThreadedView::dispatchPointerEvent (GstEvent * event)
|
||||
{
|
||||
gdouble x, y;
|
||||
gint button;
|
||||
if (!gst_navigation_event_parse_mouse_button_event (event, &button, &x, &y)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GstNavigationModifierType modifier_state;
|
||||
guint wpe_button = 0;
|
||||
if (gst_navigation_event_parse_modifier_state (event, &modifier_state)) {
|
||||
if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON1_MASK)
|
||||
wpe_button = WPE_BUTTON_PRIMARY;
|
||||
else if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON2_MASK)
|
||||
wpe_button = WPE_BUTTON_MIDDLE;
|
||||
else if (modifier_state & GST_NAVIGATION_MODIFIER_BUTTON3_MASK)
|
||||
wpe_button = WPE_BUTTON_SECONDARY;
|
||||
}
|
||||
|
||||
auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
|
||||
guint press_count = 0;
|
||||
WPEEventType type;
|
||||
if (gst_navigation_event_get_type (event) ==
|
||||
GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS) {
|
||||
press_count = wpe_view_compute_press_count (WPE_VIEW (wpe.view), x, y,
|
||||
wpe_button, timestamp);
|
||||
type = WPE_EVENT_POINTER_DOWN;
|
||||
} else {
|
||||
type = WPE_EVENT_POINTER_UP;
|
||||
}
|
||||
dispatchEvent (wpe_event_pointer_button_new (type, WPE_VIEW (wpe.view),
|
||||
WPE_INPUT_SOURCE_MOUSE, timestamp, modifiers_from_gst_event (event),
|
||||
wpe_button, x, y, press_count));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean GstWPEThreadedView::dispatchPointerMoveEvent (GstEvent * event)
|
||||
{
|
||||
gdouble x, y;
|
||||
if (!gst_navigation_event_parse_mouse_move_event (event, &x, &y)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gdouble delta_x = 0;
|
||||
gdouble delta_y = 0;
|
||||
if (m_last_pointer_position) {
|
||||
delta_x = x - m_last_pointer_position->first;
|
||||
delta_y = y - m_last_pointer_position->second;
|
||||
}
|
||||
m_last_pointer_position = { x, y };
|
||||
|
||||
auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
|
||||
dispatchEvent (wpe_event_pointer_move_new (WPE_EVENT_POINTER_MOVE,
|
||||
WPE_VIEW (wpe.view), WPE_INPUT_SOURCE_MOUSE, timestamp,
|
||||
modifiers_from_gst_event (event), x, y, delta_x, delta_y));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean GstWPEThreadedView::dispatchAxisEvent (GstEvent * event)
|
||||
{
|
||||
gdouble x, y, delta_x, delta_y;
|
||||
if (!gst_navigation_event_parse_mouse_scroll_event (event, &x, &y, &delta_x,
|
||||
&delta_y)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
|
||||
dispatchEvent (wpe_event_scroll_new (WPE_VIEW (wpe.view),
|
||||
WPE_INPUT_SOURCE_MOUSE, timestamp, modifiers_from_gst_event (event),
|
||||
delta_x, delta_y, TRUE, FALSE, x, y));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean GstWPEThreadedView::dispatchTouchEvent (GstEvent * event)
|
||||
{
|
||||
guint touch_id;
|
||||
gdouble x, y;
|
||||
if (!gst_navigation_event_parse_touch_event (event, &touch_id, &x, &y, NULL)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WPEEventType event_type = WPE_EVENT_NONE;
|
||||
switch (gst_navigation_event_get_type (event)) {
|
||||
case GST_NAVIGATION_EVENT_TOUCH_DOWN:
|
||||
event_type = WPE_EVENT_TOUCH_DOWN;
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_TOUCH_MOTION:
|
||||
event_type = WPE_EVENT_TOUCH_MOVE;
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_TOUCH_UP:
|
||||
event_type = WPE_EVENT_TOUCH_UP;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
auto timestamp = GST_TIME_AS_MSECONDS (GST_EVENT_TIMESTAMP (event));
|
||||
auto modifiers = static_cast<WPEModifiers>(_keyboard_modifiers_from_gst_event (event));
|
||||
dispatchEvent (wpe_event_touch_new (event_type, WPE_VIEW (wpe.view),
|
||||
WPE_INPUT_SOURCE_TOUCHPAD, timestamp, modifiers, touch_id, x, y));
|
||||
return TRUE;
|
||||
}
|
||||
/* *INDENT-ON* */
|
154
subprojects/gst-plugins-bad/ext/wpe2/gstwpethreadedview.h
Normal file
154
subprojects/gst-plugins-bad/ext/wpe2/gstwpethreadedview.h
Normal file
@ -0,0 +1,154 @@
|
||||
/* Copyright (C) <2018, 2025> Philippe Normand <philn@igalia.com>
|
||||
* Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <glib.h>
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
#include <gst/gl/egl/gstgldisplay_egl.h>
|
||||
#include <wpe/webkit.h>
|
||||
#include "gstwpevideosrc.h"
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
typedef struct _GstGLContext GstGLContext;
|
||||
typedef struct _GstGLDisplay GstGLDisplay;
|
||||
typedef struct _GstEGLImage GstEGLImage;
|
||||
|
||||
class GstWPEThreadedView {
|
||||
public:
|
||||
GstWPEThreadedView(WebKitWebContext *, GstWpeVideoSrc2 *, GstGLContext *,
|
||||
GstGLDisplay *, WPEDisplay *, int width, int height);
|
||||
~GstWPEThreadedView();
|
||||
|
||||
/* Used by gstwpeview */
|
||||
gboolean setPendingBuffer(WPEBuffer*, GError**);
|
||||
|
||||
/* Used by wpevideosrc */
|
||||
void resize(int width, int height);
|
||||
void loadUri(const gchar*);
|
||||
void loadData(GBytes*);
|
||||
void runJavascript(const gchar*);
|
||||
void setDrawBackground(gboolean);
|
||||
void clearBuffers();
|
||||
|
||||
GstEGLImage* image();
|
||||
GstBuffer* buffer();
|
||||
|
||||
gboolean dispatchKeyboardEvent(GstEvent*);
|
||||
gboolean dispatchPointerEvent(GstEvent*);
|
||||
gboolean dispatchPointerMoveEvent(GstEvent*);
|
||||
gboolean dispatchAxisEvent(GstEvent*);
|
||||
gboolean dispatchTouchEvent(GstEvent*);
|
||||
|
||||
/* Used by GstWPEContextThread */
|
||||
bool hasUri() const { return webkit.uri; }
|
||||
void disconnectLoadFailedSignal();
|
||||
void waitLoadCompletion();
|
||||
|
||||
GstWpeVideoSrc2 *src() const { return m_src; }
|
||||
|
||||
void notifyLoadFinished();
|
||||
|
||||
private:
|
||||
void frameComplete();
|
||||
|
||||
void dispatchEvent(WPEEvent*);
|
||||
void loadUriUnlocked(const gchar*);
|
||||
|
||||
static void s_releaseBuffer(gpointer);
|
||||
|
||||
struct {
|
||||
GstGLContext* context;
|
||||
GstGLDisplay* display;
|
||||
GstGLDisplayEGL* display_egl;
|
||||
} gst { nullptr, nullptr, nullptr };
|
||||
|
||||
struct {
|
||||
WPEView *view;
|
||||
int width;
|
||||
int height;
|
||||
} wpe { nullptr, 0, 0 };
|
||||
|
||||
struct {
|
||||
gchar* uri;
|
||||
WebKitWebView* view;
|
||||
} webkit = { nullptr, nullptr };
|
||||
|
||||
struct {
|
||||
GMutex ready_mutex;
|
||||
GCond ready_cond;
|
||||
gboolean ready;
|
||||
} threading;
|
||||
|
||||
// This mutex guards access to either egl or shm resources declared below,
|
||||
// depending on the runtime behavior.
|
||||
GMutex images_mutex;
|
||||
|
||||
struct {
|
||||
GstEGLImage* pending;
|
||||
GstEGLImage* committed;
|
||||
} egl { nullptr, nullptr };
|
||||
|
||||
struct {
|
||||
GstBuffer* pending;
|
||||
GstBuffer* committed;
|
||||
} shm { nullptr, nullptr };
|
||||
|
||||
struct {
|
||||
gulong init_ext_sigid;
|
||||
gulong extension_msg_sigid;
|
||||
} audio {0, 0};
|
||||
|
||||
GstWpeVideoSrc2 *m_src { nullptr };
|
||||
|
||||
WPEBuffer *m_pending_buffer { nullptr };
|
||||
WPEBuffer *m_committed_buffer { nullptr };
|
||||
|
||||
std::optional<std::pair<gdouble, gdouble>> m_last_pointer_position;
|
||||
};
|
||||
|
||||
class GstWPEContextThread {
|
||||
public:
|
||||
static GstWPEContextThread& singleton();
|
||||
|
||||
GstWPEContextThread();
|
||||
~GstWPEContextThread();
|
||||
|
||||
GstWPEThreadedView* createWPEView(GstWpeVideoSrc2*, GstGLContext*, GstGLDisplay*, WPEDisplay*, int width, int height);
|
||||
|
||||
template<typename Function>
|
||||
void dispatch(Function);
|
||||
|
||||
private:
|
||||
static gpointer s_viewThread(gpointer);
|
||||
struct {
|
||||
GMutex mutex;
|
||||
GCond cond;
|
||||
gboolean ready;
|
||||
GThread* thread { nullptr };
|
||||
} threading;
|
||||
|
||||
struct {
|
||||
GMainContext* context;
|
||||
GMainLoop* loop;
|
||||
WebKitWebContext* web_context;
|
||||
} glib { nullptr, nullptr, nullptr };
|
||||
};
|
65
subprojects/gst-plugins-bad/ext/wpe2/gstwpetoplevel.cpp
Normal file
65
subprojects/gst-plugins-bad/ext/wpe2/gstwpetoplevel.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
/* Copyright (C) <2025> Philippe Normand <philn@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "gstwpetoplevel.h"
|
||||
|
||||
struct _WPEToplevelGStreamer
|
||||
{
|
||||
WPEToplevel parent;
|
||||
};
|
||||
|
||||
#define wpe_toplevel_gstreamer_parent_class parent_class
|
||||
G_DEFINE_TYPE (WPEToplevelGStreamer, wpe_toplevel_gstreamer, WPE_TYPE_TOPLEVEL);
|
||||
|
||||
static gboolean
|
||||
wpe_toplevel_gstreamer_resize (WPEToplevel * toplevel, int width, int height)
|
||||
{
|
||||
wpe_toplevel_resized (toplevel, width, height);
|
||||
/* *INDENT-OFF* */
|
||||
wpe_toplevel_foreach_view(toplevel, [](WPEToplevel *toplevel, WPEView *view, gpointer) -> gboolean {
|
||||
int width, height;
|
||||
wpe_toplevel_get_size (toplevel, &width, &height);
|
||||
wpe_view_resized (view, width, height);
|
||||
return FALSE;
|
||||
}, nullptr);
|
||||
/* *INDENT-ON* */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_toplevel_gstreamer_init (WPEToplevelGStreamer * toplevel)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_toplevel_gstreamer_class_init (WPEToplevelGStreamerClass * klass)
|
||||
{
|
||||
WPEToplevelClass *toplevelClass = WPE_TOPLEVEL_CLASS (klass);
|
||||
toplevelClass->resize = wpe_toplevel_gstreamer_resize;
|
||||
}
|
||||
|
||||
WPEToplevel *
|
||||
wpe_toplevel_gstreamer_new (WPEDisplayGStreamer * display)
|
||||
{
|
||||
return WPE_TOPLEVEL (g_object_new (WPE_TYPE_TOPLEVEL_GSTREAMER, "display",
|
||||
display, nullptr));
|
||||
}
|
35
subprojects/gst-plugins-bad/ext/wpe2/gstwpetoplevel.h
Normal file
35
subprojects/gst-plugins-bad/ext/wpe2/gstwpetoplevel.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* Copyright (C) <2025> Philippe Normand <philn@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef GstWPETopLevel_h
|
||||
#define GstWPETopLevel_h
|
||||
|
||||
#include <glib-object.h>
|
||||
#include "gstwpedisplay.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define WPE_TYPE_TOPLEVEL_GSTREAMER (wpe_toplevel_gstreamer_get_type())
|
||||
G_DECLARE_FINAL_TYPE(WPEToplevelGStreamer, wpe_toplevel_gstreamer, WPE,
|
||||
TOPLEVEL_GSTREAMER, WPEToplevel)
|
||||
|
||||
WPEToplevel *wpe_toplevel_gstreamer_new(WPEDisplayGStreamer *);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* GstWPETopLevel_h */
|
829
subprojects/gst-plugins-bad/ext/wpe2/gstwpevideosrc.cpp
Normal file
829
subprojects/gst-plugins-bad/ext/wpe2/gstwpevideosrc.cpp
Normal file
@ -0,0 +1,829 @@
|
||||
/* Copyright (C) <2018, 2025> Philippe Normand <philn@igalia.com>
|
||||
* Copyright (C) <2018> Žan Doberšek <zdobersek@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-wpevideosrc2
|
||||
* @title: wpevideosrc2
|
||||
*
|
||||
* The wpevideosrc2 element is used to produce a video texture representing a web page
|
||||
* rendered off-screen by WPE.
|
||||
*
|
||||
* Software rendering support is also available. This features allows
|
||||
* wpevideosrc2 to be used on machines without GPU, and/or for testing purpose.
|
||||
* To enable software rendering support, set the `LIBGL_ALWAYS_SOFTWARE=true`
|
||||
* environment variable and make sure `video/x-raw, format=BGRA` caps are
|
||||
* negotiated by the wpevideosrc2 element.
|
||||
*
|
||||
* As the webview loading is usually not instantaneous, the wpevideosrc2 element emits
|
||||
* messages indicating the load progress, in percent. The value is an estimate
|
||||
* based on the total number of bytes expected to be received for a document,
|
||||
* including all its possible subresources and child documents. The application
|
||||
* can handle these `element` messages synchronously for instance, in order to
|
||||
* display a progress bar or other visual load indicator. The load percent value
|
||||
* is stored in the message structure as a double value named
|
||||
* `estimated-load-progress` and the structure name is `wpe-stats`.
|
||||
*
|
||||
* ## Example launch lines
|
||||
*
|
||||
* ```shell
|
||||
* gst-launch-1.0 -v wpevideosrc2 location="https://gstreamer.freedesktop.org" ! queue ! glimagesink
|
||||
* ```
|
||||
* Shows the GStreamer website homepage
|
||||
*
|
||||
* ```shell
|
||||
* LIBGL_ALWAYS_SOFTWARE=true gst-launch-1.0 -v wpevideosrc2 num-buffers=50 location="https://gstreamer.freedesktop.org" \
|
||||
* videoconvert ! pngenc ! multifilesink location=/tmp/snapshot-%05d.png
|
||||
* ```
|
||||
* Saves the first 50 video frames generated for the GStreamer website as PNG files in /tmp.
|
||||
*
|
||||
* ```shell
|
||||
* gst-play-1.0 --videosink gtkglsink web+https://gstreamer.freedesktop.org
|
||||
* ```
|
||||
* Shows the GStreamer website homepage as played with GstPlayer in a GTK+ window.
|
||||
*
|
||||
* ```shell
|
||||
* gst-launch-1.0 glvideomixer name=m sink_1::zorder=0 ! glimagesink wpevideosrc2 location="file:///tmp/asset.html" draw-background=0 \
|
||||
* ! m. videotestsrc ! queue ! glupload ! glcolorconvert ! m.
|
||||
* ```
|
||||
* Composite WPE with a video stream in a single OpenGL scene.
|
||||
*
|
||||
* ```shell
|
||||
* gst-launch-1.0 glvideomixer name=m sink_1::zorder=0 sink_0::height=818 sink_0::width=1920 ! gtkglsink \
|
||||
* wpevideosrc2 location="file:///tmp/asset.html" draw-background=0 ! m.
|
||||
* uridecodebin uri="http://example.com/Sintel.2010.1080p.mkv" name=d d. ! queue ! glupload ! glcolorconvert ! m.
|
||||
* ```
|
||||
* Composite WPE with a video stream, sink_0 pad properties have to match the video dimensions.
|
||||
*
|
||||
* ```shell
|
||||
* weston -S $HOME/weston-sock -B headless-backend.so --use-gl &
|
||||
* WAYLAND_DISPLAY=$HOME/weston-sock gst-launch-1.0 wpevideosrc2 location=https://google.com ! queue ! fakevideosink
|
||||
* ```
|
||||
* Render Google.com with WPE in a headless Weston compositor. This can be useful for server-side WPE video processing.
|
||||
*
|
||||
* Since: 1.28
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* - Better navigation events handling (would require a new GstNavigation API)
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "gstwpe.h"
|
||||
#include "gstwpevideosrc.h"
|
||||
#include <gst/gl/gl.h>
|
||||
#include <gst/gl/egl/gstglmemoryegl.h>
|
||||
#include <gst/gl/wayland/gstgldisplay_wayland.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#include "gstwpethreadedview.h"
|
||||
#include "gstwpedisplay.h"
|
||||
|
||||
#define DEFAULT_WIDTH 1920
|
||||
#define DEFAULT_HEIGHT 1080
|
||||
#define DEFAULT_FPS_N 30
|
||||
#define DEFAULT_FPS_D 1
|
||||
#define DEFAULT_DRAW_BACKGROUND TRUE
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_LOCATION,
|
||||
PROP_DRAW_BACKGROUND
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
SIGNAL_WPE_VIEW_CREATED,
|
||||
SIGNAL_CONFIGURE_WEB_VIEW,
|
||||
SIGNAL_LOAD_BYTES,
|
||||
SIGNAL_RUN_JAVASCRIPT,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
static guint gst_wpe_video_src_signals[LAST_SIGNAL] = { 0 };
|
||||
|
||||
struct _GstWpeVideoSrc2
|
||||
{
|
||||
GstGLBaseSrc parent;
|
||||
|
||||
/* properties */
|
||||
gchar *location;
|
||||
gboolean draw_background;
|
||||
|
||||
GBytes *bytes;
|
||||
gboolean gl_enabled;
|
||||
|
||||
gint64 n_frames; /* total frames sent */
|
||||
|
||||
WPEDisplay *display;
|
||||
GstWPEThreadedView *view;
|
||||
|
||||
GMutex lock;
|
||||
};
|
||||
|
||||
#define WPE_LOCK(o) g_mutex_lock(&(o)->lock)
|
||||
#define WPE_UNLOCK(o) g_mutex_unlock(&(o)->lock)
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (wpe_video_src_debug);
|
||||
#define GST_CAT_DEFAULT wpe_video_src_debug
|
||||
|
||||
#define gst_wpe_video_src_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstWpeVideoSrc2, gst_wpe_video_src, GST_TYPE_GL_BASE_SRC);
|
||||
|
||||
#define WPE_RAW_CAPS "video/x-raw, " \
|
||||
"format = (string) BGRA, " \
|
||||
"width = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"height = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"framerate = " GST_VIDEO_FPS_RANGE ", " \
|
||||
"pixel-aspect-ratio = (fraction)1/1"
|
||||
|
||||
#define WPE_GL_CAPS "video/x-raw(memory:GLMemory), " \
|
||||
"format = (string) RGBA, " \
|
||||
"width = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"height = " GST_VIDEO_SIZE_RANGE ", " \
|
||||
"framerate = " GST_VIDEO_FPS_RANGE ", " \
|
||||
"pixel-aspect-ratio = (fraction)1/1, texture-target = (string)2D"
|
||||
|
||||
#define WPE_VIDEO_SRC_CAPS WPE_GL_CAPS "; " WPE_RAW_CAPS
|
||||
#define WPE_VIDEO_SRC_DOC_CAPS WPE_GL_CAPS "; video/x-raw, format = (string) BGRA"
|
||||
|
||||
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (WPE_VIDEO_SRC_CAPS));
|
||||
|
||||
#define GST_ELEMENT_PROGRESS(el, type, code, text) \
|
||||
G_STMT_START { \
|
||||
gchar *__txt = _gst_element_error_printf text; \
|
||||
gst_element_post_message( \
|
||||
GST_ELEMENT_CAST(el), \
|
||||
gst_message_new_progress(GST_OBJECT_CAST(el), \
|
||||
GST_PROGRESS_TYPE_##type, code, __txt)); \
|
||||
g_free(__txt); \
|
||||
} \
|
||||
G_STMT_END
|
||||
|
||||
static GstFlowReturn
|
||||
gst_wpe_video_src_create (GstBaseSrc * bsrc, guint64 offset, guint length,
|
||||
GstBuffer ** buf)
|
||||
{
|
||||
GstGLBaseSrc *gl_src = GST_GL_BASE_SRC (bsrc);
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (bsrc);
|
||||
GstFlowReturn ret = GST_FLOW_ERROR;
|
||||
GstBuffer *locked_buffer;
|
||||
GstClockTime next_time;
|
||||
gint64 ts_offset = 0;
|
||||
|
||||
WPE_LOCK (src);
|
||||
if (src->gl_enabled) {
|
||||
WPE_UNLOCK (src);
|
||||
return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, create, (bsrc,
|
||||
offset, length, buf), ret);
|
||||
}
|
||||
|
||||
locked_buffer = src->view->buffer ();
|
||||
if (locked_buffer == NULL) {
|
||||
WPE_UNLOCK (src);
|
||||
GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
|
||||
("WPE View did not render a buffer"), (NULL));
|
||||
return ret;
|
||||
}
|
||||
*buf = gst_buffer_copy_deep (locked_buffer);
|
||||
|
||||
g_object_get (gl_src, "timestamp-offset", &ts_offset, NULL);
|
||||
|
||||
/* The following code mimics the behaviour of GLBaseSrc::fill */
|
||||
GST_BUFFER_TIMESTAMP (*buf) = ts_offset + gl_src->running_time;
|
||||
GST_BUFFER_OFFSET (*buf) = src->n_frames;
|
||||
src->n_frames++;
|
||||
GST_BUFFER_OFFSET_END (*buf) = src->n_frames;
|
||||
if (gl_src->out_info.fps_n) {
|
||||
next_time = gst_util_uint64_scale_int (src->n_frames * GST_SECOND,
|
||||
gl_src->out_info.fps_d, gl_src->out_info.fps_n);
|
||||
GST_BUFFER_DURATION (*buf) = next_time - gl_src->running_time;
|
||||
} else {
|
||||
next_time = ts_offset;
|
||||
GST_BUFFER_DURATION (*buf) = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
|
||||
GST_LOG_OBJECT (src, "Created buffer from SHM %" GST_PTR_FORMAT, *buf);
|
||||
|
||||
gl_src->running_time = next_time;
|
||||
|
||||
ret = GST_FLOW_OK;
|
||||
WPE_UNLOCK (src);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GQuark
|
||||
_egl_image_quark (void)
|
||||
{
|
||||
static GQuark quark = 0;
|
||||
|
||||
if (!quark)
|
||||
quark = g_quark_from_static_string ("GstWPEEGLImage");
|
||||
return quark;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_fill_memory (GstGLBaseSrc * bsrc, GstGLMemory * memory)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (bsrc);
|
||||
const GstGLFuncs *gl;
|
||||
guint tex_id;
|
||||
GstEGLImage *locked_image;
|
||||
|
||||
if (!gst_gl_context_check_feature (GST_GL_CONTEXT (bsrc->context),
|
||||
"EGL_KHR_image_base")) {
|
||||
GST_ERROR_OBJECT (src, "EGL_KHR_image_base is not supported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WPE_LOCK (src);
|
||||
|
||||
gl = bsrc->context->gl_vtable;
|
||||
tex_id = gst_gl_memory_get_texture_id (memory);
|
||||
locked_image = src->view->image ();
|
||||
|
||||
if (!locked_image) {
|
||||
WPE_UNLOCK (src);
|
||||
return TRUE;
|
||||
}
|
||||
// The EGLImage is implicitely associated with the memory we're filling, so we
|
||||
// need to ensure their life cycles are tied.
|
||||
gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (memory), _egl_image_quark (),
|
||||
gst_egl_image_ref (locked_image), (GDestroyNotify) gst_egl_image_unref);
|
||||
|
||||
gl->ActiveTexture (GL_TEXTURE0 + memory->plane);
|
||||
gl->BindTexture (GL_TEXTURE_2D, tex_id);
|
||||
gl->EGLImageTargetTexture2D (GL_TEXTURE_2D,
|
||||
gst_egl_image_get_image (locked_image));
|
||||
WPE_UNLOCK (src);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_start (GstWpeVideoSrc2 * src)
|
||||
{
|
||||
GstGLContext *context = NULL;
|
||||
GstGLDisplay *display = NULL;
|
||||
GstGLBaseSrc *base_src = GST_GL_BASE_SRC (src);
|
||||
gboolean created_view = FALSE;
|
||||
GBytes *bytes;
|
||||
|
||||
GST_ELEMENT_PROGRESS (src, START, "open", ("Starting up"));
|
||||
GST_INFO_OBJECT (src, "Starting up");
|
||||
WPE_LOCK (src);
|
||||
|
||||
if (src->gl_enabled) {
|
||||
context = base_src->context;
|
||||
display = base_src->display;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (src, "Will %sfill GLMemories",
|
||||
src->gl_enabled ? "" : "NOT ");
|
||||
|
||||
auto & thread = GstWPEContextThread::singleton ();
|
||||
|
||||
if (!src->view) {
|
||||
GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Creating WPE WebView"));
|
||||
|
||||
GError *error = nullptr;
|
||||
wpe_display_gstreamer_set_gl (src->display, display, context);
|
||||
|
||||
if (!wpe_display_connect (src->display, &error)) {
|
||||
WPE_UNLOCK (src);
|
||||
GST_ELEMENT_PROGRESS (src, ERROR, "open",
|
||||
("WPE display initialisation failed"));
|
||||
GST_ELEMENT_ERROR (src, RESOURCE, FAILED,
|
||||
("Display initialisation failed: %s", error->message), (NULL));
|
||||
g_error_free (error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
src->view =
|
||||
thread.createWPEView (src, context, display, src->display,
|
||||
GST_VIDEO_INFO_WIDTH (&base_src->out_info),
|
||||
GST_VIDEO_INFO_HEIGHT (&base_src->out_info));
|
||||
created_view = TRUE;
|
||||
GST_DEBUG_OBJECT (src, "created view %p", src->view);
|
||||
GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("WPE WebView is ready"));
|
||||
}
|
||||
|
||||
if (!created_view) {
|
||||
GST_INFO_OBJECT (src,
|
||||
"Re-starting after re-negotiation, clearing cached SHM buffers");
|
||||
src->view->clearBuffers ();
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (src);
|
||||
bytes = src->bytes;
|
||||
src->bytes = NULL;
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
|
||||
if (bytes != NULL) {
|
||||
GST_ELEMENT_PROGRESS (src, CONTINUE, "open", ("Loading HTML data"));
|
||||
src->view->loadData (bytes);
|
||||
g_bytes_unref (bytes);
|
||||
}
|
||||
|
||||
if (created_view) {
|
||||
src->n_frames = 0;
|
||||
}
|
||||
WPE_UNLOCK (src);
|
||||
GST_ELEMENT_PROGRESS (src, COMPLETE, "open", ("Ready to produce buffers"));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_decide_allocation (GstBaseSrc * base_src, GstQuery * query)
|
||||
{
|
||||
GstGLBaseSrc *gl_src = GST_GL_BASE_SRC (base_src);
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
GstCapsFeatures *caps_features;
|
||||
|
||||
WPE_LOCK (src);
|
||||
caps_features = gst_caps_get_features (gl_src->out_caps, 0);
|
||||
if (caps_features != NULL
|
||||
&& gst_caps_features_contains (caps_features,
|
||||
GST_CAPS_FEATURE_MEMORY_GL_MEMORY)) {
|
||||
src->gl_enabled = TRUE;
|
||||
} else {
|
||||
src->gl_enabled = FALSE;
|
||||
}
|
||||
|
||||
if (src->gl_enabled) {
|
||||
WPE_UNLOCK (src);
|
||||
return GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, decide_allocation,
|
||||
(base_src, query), FALSE);
|
||||
}
|
||||
WPE_UNLOCK (src);
|
||||
return gst_wpe_video_src_start (src);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_gl_start (GstGLBaseSrc * base_src)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
return gst_wpe_video_src_start (src);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_stop_unlocked (GstWpeVideoSrc2 * src)
|
||||
{
|
||||
if (src->view) {
|
||||
GST_DEBUG_OBJECT (src, "deleting view %p", src->view);
|
||||
delete src->view;
|
||||
src->view = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_gl_stop (GstGLBaseSrc * base_src)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
|
||||
WPE_LOCK (src);
|
||||
gst_wpe_video_src_stop_unlocked (src);
|
||||
WPE_UNLOCK (src);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_stop (GstBaseSrc * base_src)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
|
||||
/* we can call this always, GstGLBaseSrc is smart enough to not crash if
|
||||
* gst_gl_base_src_gl_start() has not been called from chaining up
|
||||
* gst_wpe_video_src_decide_allocation() */
|
||||
if (!GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, stop, (base_src),
|
||||
FALSE))
|
||||
return FALSE;
|
||||
|
||||
WPE_LOCK (src);
|
||||
|
||||
/* if gl-enabled, gst_wpe_video_src_stop_unlocked() would have already been called
|
||||
* inside gst_wpe_video_src_gl_stop() from the base class stopping the OpenGL
|
||||
* context */
|
||||
if (!src->gl_enabled)
|
||||
gst_wpe_video_src_stop_unlocked (src);
|
||||
|
||||
WPE_UNLOCK (src);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_wpe_video_src_fixate (GstBaseSrc * base_src, GstCaps * combined_caps)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
GstStructure *structure;
|
||||
gint width, height;
|
||||
GstCaps *caps;
|
||||
|
||||
/* In situation where software GL support is explicitly requested, select raw
|
||||
* caps, otherwise perform default caps negotiation. Unfortunately at this
|
||||
* point we don't know yet if a GL context will be usable or not, so we can't
|
||||
* check the element GstContext.
|
||||
*/
|
||||
if (!g_strcmp0 (g_getenv ("LIBGL_ALWAYS_SOFTWARE"), "true")) {
|
||||
caps = gst_caps_from_string (WPE_RAW_CAPS);
|
||||
} else {
|
||||
caps = gst_caps_make_writable (combined_caps);
|
||||
}
|
||||
|
||||
structure = gst_caps_get_structure (caps, 0);
|
||||
|
||||
gst_structure_fixate_field_nearest_int (structure, "width", DEFAULT_WIDTH);
|
||||
gst_structure_fixate_field_nearest_int (structure, "height", DEFAULT_HEIGHT);
|
||||
|
||||
if (gst_structure_has_field (structure, "framerate"))
|
||||
gst_structure_fixate_field_nearest_fraction (structure, "framerate",
|
||||
DEFAULT_FPS_N, DEFAULT_FPS_D);
|
||||
else
|
||||
gst_structure_set (structure, "framerate", GST_TYPE_FRACTION, DEFAULT_FPS_N,
|
||||
DEFAULT_FPS_D, NULL);
|
||||
|
||||
caps = GST_BASE_SRC_CLASS (parent_class)->fixate (base_src, caps);
|
||||
GST_INFO_OBJECT (base_src, "Fixated caps to %" GST_PTR_FORMAT, caps);
|
||||
|
||||
if (src->view) {
|
||||
gst_structure_get (structure, "width", G_TYPE_INT, &width, "height",
|
||||
G_TYPE_INT, &height, NULL);
|
||||
src->view->resize (width, height);
|
||||
}
|
||||
return caps;
|
||||
}
|
||||
|
||||
void
|
||||
gst_wpe_video_src_configure_web_view (GstWpeVideoSrc2 * src,
|
||||
WebKitWebView * webview)
|
||||
{
|
||||
GValue args[2] = { {0}, {0} };
|
||||
|
||||
g_value_init (&args[0], GST_TYPE_ELEMENT);
|
||||
g_value_set_object (&args[0], src);
|
||||
g_value_init (&args[1], G_TYPE_OBJECT);
|
||||
g_value_set_object (&args[1], webview);
|
||||
|
||||
g_signal_emitv (args, gst_wpe_video_src_signals[SIGNAL_CONFIGURE_WEB_VIEW], 0,
|
||||
NULL);
|
||||
|
||||
g_value_unset (&args[0]);
|
||||
g_value_unset (&args[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_run_javascript (GstWpeVideoSrc2 * src, const gchar * script)
|
||||
{
|
||||
if (src->view && GST_STATE (GST_ELEMENT_CAST (src)) > GST_STATE_NULL) {
|
||||
GST_INFO_OBJECT (src, "running javascript");
|
||||
src->view->runJavascript (script);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_load_bytes (GstWpeVideoSrc2 * src, GBytes * bytes)
|
||||
{
|
||||
if (src->view && GST_STATE (GST_ELEMENT_CAST (src)) > GST_STATE_NULL) {
|
||||
src->view->loadData (bytes);
|
||||
} else {
|
||||
GST_OBJECT_LOCK (src);
|
||||
if (src->bytes)
|
||||
g_bytes_unref (src->bytes);
|
||||
src->bytes = g_bytes_ref (bytes);
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_set_location (GstWpeVideoSrc2 * src, const gchar * location,
|
||||
GError ** error)
|
||||
{
|
||||
GST_OBJECT_LOCK (src);
|
||||
g_free (src->location);
|
||||
src->location = g_strdup (location);
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
|
||||
if (src->view)
|
||||
src->view->loadUri (location);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_set_draw_background (GstWpeVideoSrc2 * src,
|
||||
gboolean draw_background)
|
||||
{
|
||||
GST_OBJECT_LOCK (src);
|
||||
src->draw_background = draw_background;
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
|
||||
if (src->view)
|
||||
src->view->setDrawBackground (draw_background);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_LOCATION:
|
||||
{
|
||||
const gchar *location;
|
||||
|
||||
location = g_value_get_string (value);
|
||||
if (location == NULL) {
|
||||
GST_WARNING_OBJECT (src, "location property cannot be NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gst_wpe_video_src_set_location (src, location, NULL)) {
|
||||
GST_WARNING_OBJECT (src, "badly formatted location");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PROP_DRAW_BACKGROUND:
|
||||
gst_wpe_video_src_set_draw_background (src, g_value_get_boolean (value));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_get_property (GObject * object, guint prop_id, GValue * value,
|
||||
GParamSpec * pspec)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_LOCATION:
|
||||
GST_OBJECT_LOCK (src);
|
||||
g_value_set_string (value, src->location);
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
break;
|
||||
case PROP_DRAW_BACKGROUND:
|
||||
GST_OBJECT_LOCK (src);
|
||||
g_value_set_boolean (value, src->draw_background);
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_event (GstBaseSrc * base_src, GstEvent * event)
|
||||
{
|
||||
gboolean ret = FALSE;
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
|
||||
if (src->view && GST_EVENT_TYPE (event) == GST_EVENT_NAVIGATION) {
|
||||
GST_DEBUG_OBJECT (src, "Processing event %" GST_PTR_FORMAT, event);
|
||||
switch (gst_navigation_event_get_type (event)) {
|
||||
case GST_NAVIGATION_EVENT_KEY_PRESS:
|
||||
case GST_NAVIGATION_EVENT_KEY_RELEASE:
|
||||
ret = src->view->dispatchKeyboardEvent (event);
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_MOUSE_BUTTON_PRESS:
|
||||
case GST_NAVIGATION_EVENT_MOUSE_BUTTON_RELEASE:
|
||||
ret = src->view->dispatchPointerEvent (event);
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_MOUSE_MOVE:
|
||||
ret = src->view->dispatchPointerMoveEvent (event);
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_MOUSE_SCROLL:
|
||||
ret = src->view->dispatchAxisEvent (event);
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_TOUCH_DOWN:
|
||||
case GST_NAVIGATION_EVENT_TOUCH_MOTION:
|
||||
case GST_NAVIGATION_EVENT_TOUCH_UP:
|
||||
ret = src->view->dispatchTouchEvent (event);
|
||||
break;
|
||||
case GST_NAVIGATION_EVENT_TOUCH_FRAME:
|
||||
case GST_NAVIGATION_EVENT_TOUCH_CANCEL:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
ret =
|
||||
GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, event, (base_src,
|
||||
event), FALSE);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wpe_video_src_query (GstBaseSrc * base_src, GstQuery * query)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (base_src);
|
||||
GstGLBaseSrc *gl_src = GST_GL_BASE_SRC (base_src);
|
||||
gboolean ret = FALSE;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_LATENCY:{
|
||||
GST_OBJECT_LOCK (src);
|
||||
if (gl_src->out_info.fps_n > 0) {
|
||||
GstClockTime latency;
|
||||
|
||||
latency = gst_util_uint64_scale (GST_SECOND, gl_src->out_info.fps_d,
|
||||
gl_src->out_info.fps_n);
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
gst_query_set_latency (query,
|
||||
gst_base_src_is_live (GST_BASE_SRC_CAST (src)), latency,
|
||||
GST_CLOCK_TIME_NONE);
|
||||
GST_DEBUG_OBJECT (src, "Reporting latency of %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (latency));
|
||||
ret = TRUE;
|
||||
} else {
|
||||
GST_OBJECT_UNLOCK (src);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = GST_CALL_PARENT_WITH_DEFAULT (GST_BASE_SRC_CLASS, query,
|
||||
(base_src, query), FALSE);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
on_view_created (WPEDisplayGStreamer *, WPEView * view, gpointer user_data)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (user_data);
|
||||
GValue args[2] = { {0}, {0}
|
||||
};
|
||||
|
||||
g_value_init (&args[0], GST_TYPE_WPE_VIDEO_SRC);
|
||||
g_value_set_object (&args[0], src);
|
||||
|
||||
g_value_init (&args[1], WPE_TYPE_VIEW);
|
||||
g_value_set_object (&args[1], view);
|
||||
|
||||
g_signal_emitv (args, gst_wpe_video_src_signals[SIGNAL_WPE_VIEW_CREATED], 0,
|
||||
NULL);
|
||||
|
||||
g_value_unset (&args[0]);
|
||||
g_value_unset (&args[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_init (GstWpeVideoSrc2 * src)
|
||||
{
|
||||
src->draw_background = DEFAULT_DRAW_BACKGROUND;
|
||||
src->location = g_strdup (DEFAULT_LOCATION);
|
||||
src->display = wpe_display_gstreamer_new ();
|
||||
|
||||
g_signal_connect (src->display, "wpe-view-created",
|
||||
G_CALLBACK (on_view_created), src);
|
||||
|
||||
gst_base_src_set_live (GST_BASE_SRC_CAST (src), TRUE);
|
||||
|
||||
g_mutex_init (&src->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_finalize (GObject * object)
|
||||
{
|
||||
GstWpeVideoSrc2 *src = GST_WPE_VIDEO_SRC (object);
|
||||
|
||||
g_free (src->location);
|
||||
g_clear_pointer (&src->bytes, g_bytes_unref);
|
||||
g_mutex_clear (&src->lock);
|
||||
g_clear_pointer (&src->display, g_object_unref);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wpe_video_src_class_init (GstWpeVideoSrc2Class * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
||||
GstGLBaseSrcClass *gl_base_src_class = GST_GL_BASE_SRC_CLASS (klass);
|
||||
GstBaseSrcClass *base_src_class = GST_BASE_SRC_CLASS (klass);
|
||||
GstPadTemplate *tmpl;
|
||||
GstCaps *doc_caps;
|
||||
|
||||
gobject_class->set_property = gst_wpe_video_src_set_property;
|
||||
gobject_class->get_property = gst_wpe_video_src_get_property;
|
||||
gobject_class->finalize = gst_wpe_video_src_finalize;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_LOCATION,
|
||||
g_param_spec_string ("location", "location",
|
||||
"The URL to display",
|
||||
DEFAULT_LOCATION,
|
||||
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
g_object_class_install_property (gobject_class, PROP_DRAW_BACKGROUND,
|
||||
g_param_spec_boolean ("draw-background", "Draws the background",
|
||||
"Whether to draw the WebView background", DEFAULT_DRAW_BACKGROUND,
|
||||
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class,
|
||||
"WPE source", "Source/Video",
|
||||
"Creates a video stream from a WPE browser",
|
||||
"Philippe Normand <philn@igalia.com>, Žan Doberšek <zdobersek@igalia.com>");
|
||||
|
||||
tmpl = gst_static_pad_template_get (&src_factory);
|
||||
gst_element_class_add_pad_template (gstelement_class, tmpl);
|
||||
|
||||
base_src_class->fixate = GST_DEBUG_FUNCPTR (gst_wpe_video_src_fixate);
|
||||
base_src_class->create = GST_DEBUG_FUNCPTR (gst_wpe_video_src_create);
|
||||
base_src_class->decide_allocation =
|
||||
GST_DEBUG_FUNCPTR (gst_wpe_video_src_decide_allocation);
|
||||
base_src_class->stop = GST_DEBUG_FUNCPTR (gst_wpe_video_src_stop);
|
||||
base_src_class->event = GST_DEBUG_FUNCPTR (gst_wpe_video_src_event);
|
||||
base_src_class->query = GST_DEBUG_FUNCPTR (gst_wpe_video_src_query);
|
||||
|
||||
gl_base_src_class->supported_gl_api =
|
||||
static_cast < GstGLAPI >
|
||||
(GST_GL_API_OPENGL | GST_GL_API_OPENGL3 | GST_GL_API_GLES2);
|
||||
gl_base_src_class->gl_start = GST_DEBUG_FUNCPTR (gst_wpe_video_src_gl_start);
|
||||
gl_base_src_class->gl_stop = GST_DEBUG_FUNCPTR (gst_wpe_video_src_gl_stop);
|
||||
gl_base_src_class->fill_gl_memory =
|
||||
GST_DEBUG_FUNCPTR (gst_wpe_video_src_fill_memory);
|
||||
|
||||
doc_caps = gst_caps_from_string (WPE_VIDEO_SRC_DOC_CAPS);
|
||||
gst_pad_template_set_documentation_caps (tmpl, doc_caps);
|
||||
gst_clear_caps (&doc_caps);
|
||||
|
||||
/**
|
||||
* GstWpeVideoSrc2::wpe-view-created:
|
||||
* @src: the object which received the signal
|
||||
* @view: the #WPEView that was created
|
||||
*
|
||||
* This signal can be used to hook into the WPEView signals as soon as it was
|
||||
* created.
|
||||
*/
|
||||
gst_wpe_video_src_signals[SIGNAL_WPE_VIEW_CREATED] =
|
||||
g_signal_new ("wpe-view-created", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, WPE_TYPE_VIEW);
|
||||
|
||||
/**
|
||||
* GstWpeVideoSrc2::configure-web-view:
|
||||
* @src: the object which received the signal
|
||||
* @webview: the webView
|
||||
*
|
||||
* Allow application to configure the webView settings.
|
||||
*/
|
||||
gst_wpe_video_src_signals[SIGNAL_CONFIGURE_WEB_VIEW] =
|
||||
g_signal_new ("configure-web-view", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_OBJECT);
|
||||
|
||||
/**
|
||||
* GstWpeVideoSrc2::load-bytes:
|
||||
* @src: the object which received the signal
|
||||
* @bytes: the GBytes data to load
|
||||
*
|
||||
* Load the specified bytes into the internal webView.
|
||||
*/
|
||||
gst_wpe_video_src_signals[SIGNAL_LOAD_BYTES] =
|
||||
g_signal_new_class_handler ("load-bytes", G_TYPE_FROM_CLASS (klass),
|
||||
static_cast < GSignalFlags > (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
|
||||
G_CALLBACK (gst_wpe_video_src_load_bytes), NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_BYTES);
|
||||
|
||||
/**
|
||||
* GstWpeVideoSrc2::run-javascript:
|
||||
* @src: the object which received the signal
|
||||
* @script: the script to run
|
||||
*
|
||||
* Asynchronously run script in the context of the current page on the
|
||||
* internal webView.
|
||||
*
|
||||
*/
|
||||
gst_wpe_video_src_signals[SIGNAL_RUN_JAVASCRIPT] =
|
||||
g_signal_new_class_handler ("run-javascript", G_TYPE_FROM_CLASS (klass),
|
||||
static_cast < GSignalFlags > (G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION),
|
||||
G_CALLBACK (gst_wpe_video_src_run_javascript), NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_STRING);
|
||||
}
|
17
subprojects/gst-plugins-bad/ext/wpe2/gstwpevideosrc.h
Normal file
17
subprojects/gst-plugins-bad/ext/wpe2/gstwpevideosrc.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <wpe/webkit.h>
|
||||
#include <gst/gl/gl.h>
|
||||
#include "gstwpeview.h"
|
||||
|
||||
typedef struct _GstWpeVideoSrc2 GstWpeVideoSrc2;
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
#define GST_TYPE_WPE_VIDEO_SRC (gst_wpe_video_src_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GstWpeVideoSrc2, gst_wpe_video_src, GST, WPE_VIDEO_SRC, GstGLBaseSrc);
|
||||
|
||||
void gst_wpe_video_src_configure_web_view (GstWpeVideoSrc2 * src, WebKitWebView * webview);
|
||||
|
||||
G_END_DECLS
|
69
subprojects/gst-plugins-bad/ext/wpe2/gstwpeview.cpp
Normal file
69
subprojects/gst-plugins-bad/ext/wpe2/gstwpeview.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
/* Copyright (C) <2025> Philippe Normand <philn@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "gstwpeview.h"
|
||||
#include "gstwpethreadedview.h"
|
||||
|
||||
struct _WPEViewGStreamer
|
||||
{
|
||||
WPEView parent;
|
||||
|
||||
GstWPEThreadedView *client;
|
||||
};
|
||||
|
||||
#define wpe_view_gstreamer_parent_class parent_class
|
||||
G_DEFINE_TYPE (WPEViewGStreamer, wpe_view_gstreamer, WPE_TYPE_VIEW);
|
||||
|
||||
static gboolean
|
||||
wpe_view_gstreamer_render_buffer (WPEView * view, WPEBuffer * buffer,
|
||||
const WPERectangle *, guint, GError ** error)
|
||||
{
|
||||
auto self = WPE_VIEW_GSTREAMER (view);
|
||||
// TODO: Add support for damage rects.
|
||||
return self->client->setPendingBuffer (buffer, error);
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_view_gstreamer_init (WPEViewGStreamer * view)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
wpe_view_gstreamer_class_init (WPEViewGStreamerClass * klass)
|
||||
{
|
||||
WPEViewClass *viewClass = WPE_VIEW_CLASS (klass);
|
||||
viewClass->render_buffer = wpe_view_gstreamer_render_buffer;
|
||||
}
|
||||
|
||||
WPEView *
|
||||
wpe_view_gstreamer_new (WPEDisplayGStreamer * display)
|
||||
{
|
||||
return WPE_VIEW (g_object_new (WPE_TYPE_VIEW_GSTREAMER, "display", display,
|
||||
nullptr));
|
||||
}
|
||||
|
||||
void
|
||||
wpe_view_gstreamer_set_client (WPEViewGStreamer * view,
|
||||
GstWPEThreadedView * client)
|
||||
{
|
||||
view->client = client;
|
||||
}
|
41
subprojects/gst-plugins-bad/ext/wpe2/gstwpeview.h
Normal file
41
subprojects/gst-plugins-bad/ext/wpe2/gstwpeview.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* Copyright (C) <2025> Philippe Normand <philn@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef GstWPEView_h
|
||||
#define GstWPEView_h
|
||||
|
||||
#include <glib-object.h>
|
||||
#include "gstwpedisplay.h"
|
||||
|
||||
class GstWPEThreadedView;
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define WPE_TYPE_VIEW_GSTREAMER (wpe_view_gstreamer_get_type())
|
||||
G_DECLARE_FINAL_TYPE(WPEViewGStreamer, wpe_view_gstreamer, WPE,
|
||||
VIEW_GSTREAMER, WPEView)
|
||||
|
||||
typedef struct _WPEDisplayGStreamer WPEDisplayGStreamer;
|
||||
|
||||
WPEView *wpe_view_gstreamer_new(WPEDisplayGStreamer *);
|
||||
|
||||
void wpe_view_gstreamer_set_client(WPEViewGStreamer*, GstWPEThreadedView*);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* GstWPEView_h */
|
60
subprojects/gst-plugins-bad/ext/wpe2/meson.build
Normal file
60
subprojects/gst-plugins-bad/ext/wpe2/meson.build
Normal file
@ -0,0 +1,60 @@
|
||||
wpe_sources = [
|
||||
'gstwpethreadedview.cpp','gstwpe2.cpp',
|
||||
'gstwpevideosrc.cpp',
|
||||
'gstwpedisplay.cpp',
|
||||
'gstwpeview.cpp',
|
||||
'gstwpetoplevel.cpp',
|
||||
]
|
||||
|
||||
wpe_headers = [
|
||||
'gstwpevideosrc.h',
|
||||
'gstwpe2.h',
|
||||
'gstwpethreadedview.h',
|
||||
]
|
||||
|
||||
doc_sources = []
|
||||
foreach s: wpe_sources + wpe_headers
|
||||
doc_sources += meson.current_source_dir() / s
|
||||
endforeach
|
||||
|
||||
plugin_sources += {
|
||||
'wpe': pathsep.join(doc_sources)
|
||||
}
|
||||
|
||||
wpe_feat = get_option('wpe2').require(gstgl_dep.found(),
|
||||
error_message : 'wpe plugin enabled but GL support was not detected')
|
||||
|
||||
if not wpe_feat.allowed()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
wpewebkit_dep = dependency('wpe-webkit-2.0', version: '>=2.46.0', required: wpe_feat)
|
||||
if not wpewebkit_dep.found()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
# GstWPE2 makes use of the new WPEPlatform API, as it is optional though we need
|
||||
# to explicitely check for it.
|
||||
wpe_platform_dep = dependency('wpe-platform-2.0', required: wpe_feat)
|
||||
if not wpe_platform_dep.found()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
egl_dep = dependency('egl', required : wpe_feat)
|
||||
xkbcommon_dep = dependency('xkbcommon', version : '>= 0.8', required : wpe_feat)
|
||||
|
||||
if not (egl_dep.found() and xkbcommon_dep.found())
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
gstwpe = library('gstwpe2',
|
||||
wpe_sources,
|
||||
override_options : ['cpp_std=c++17'],
|
||||
dependencies : [egl_dep, wpewebkit_dep, gstallocators_dep, gstaudio_dep, gstvideo_dep,
|
||||
gstbase_dep, gstgl_dep, xkbcommon_dep],
|
||||
cpp_args : gst_plugins_bad_args + ['-DHAVE_CONFIG_H=1'],
|
||||
include_directories : [configinc],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir)
|
||||
|
||||
plugins += [gstwpe]
|
@ -206,6 +206,7 @@ option(
|
||||
choices: ['auto', '1.0', '1.1', '2.0'],
|
||||
description: 'WPE WebKit API to target (1.0 = soup2, 1.1/2.0 = soup3)'
|
||||
)
|
||||
option('wpe2', type : 'feature', value : 'auto', description : 'WPE Web browser plugin')
|
||||
|
||||
option('magicleap', type : 'feature', value : 'auto', description : 'Magic Leap platform support')
|
||||
option('v4l2codecs', type : 'feature', value : 'auto', description : 'Video4Linux Stateless CODECs support')
|
||||
|
Loading…
x
Reference in New Issue
Block a user