From 98fdd7c58bcc829da6fe62b3e0910318aa397ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Sun, 27 Apr 2025 21:11:54 +0200 Subject: [PATCH] clapper: Introduce "ClapperEnhancerProxy" objects Add support for configuring Clapper Enhancers. In order to do that, introduce enhancer proxy object that act as intermediary between player and enhancer plugin. We cannot give direct access to enhancer instances from code, since they are managed (created and destroyed) by player as/when needed. Also due to some interpreted languages not working with multiple threads. Instead, give proxy objects that will store each enhancer configuration to be applied when an enhancer instance is created. With this, implementations also gain ability to browse available enhancers, see what they support and change their properties. Enhancers are now also assigned to player, instead of being only global. This allows to configure them separately on a per player instance basis. Writing configurable enhancers is super easy too, as all plugin has to do is install standard GParamSpec properties to its class with a corresponding gschema file (for global props only) and its done. --- .../clapper-enhancer-proxy-list-private.h | 41 + src/lib/clapper/clapper-enhancer-proxy-list.c | 329 ++++++ src/lib/clapper/clapper-enhancer-proxy-list.h | 53 + .../clapper/clapper-enhancer-proxy-private.h | 47 + src/lib/clapper/clapper-enhancer-proxy.c | 1035 +++++++++++++++++ src/lib/clapper/clapper-enhancer-proxy.h | 80 ++ .../clapper-enhancers-loader-private.h | 16 +- src/lib/clapper/clapper-enhancers-loader.c | 359 +----- src/lib/clapper/clapper-enums.h | 24 + src/lib/clapper/clapper-player-private.h | 2 + src/lib/clapper/clapper-player.c | 51 +- src/lib/clapper/clapper-player.h | 4 + src/lib/clapper/clapper.c | 73 +- src/lib/clapper/clapper.h | 7 +- .../gst/clapper-enhancer-director-private.h | 2 +- .../clapper/gst/clapper-enhancer-director.c | 73 +- src/lib/clapper/gst/clapper-enhancer-src.c | 220 +++- src/lib/clapper/gst/clapper-plugin.c | 39 +- src/lib/clapper/meson.build | 9 +- 19 files changed, 2108 insertions(+), 356 deletions(-) create mode 100644 src/lib/clapper/clapper-enhancer-proxy-list-private.h create mode 100644 src/lib/clapper/clapper-enhancer-proxy-list.c create mode 100644 src/lib/clapper/clapper-enhancer-proxy-list.h create mode 100644 src/lib/clapper/clapper-enhancer-proxy-private.h create mode 100644 src/lib/clapper/clapper-enhancer-proxy.c create mode 100644 src/lib/clapper/clapper-enhancer-proxy.h diff --git a/src/lib/clapper/clapper-enhancer-proxy-list-private.h b/src/lib/clapper/clapper-enhancer-proxy-list-private.h new file mode 100644 index 00000000..c25b4024 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-list-private.h @@ -0,0 +1,41 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + +#include "clapper-enhancer-proxy-list.h" +#include "clapper-enhancer-proxy.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +ClapperEnhancerProxyList * clapper_enhancer_proxy_list_new_named (const gchar *name); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *list, ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *list); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *list); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy-list.c b/src/lib/clapper/clapper-enhancer-proxy-list.c new file mode 100644 index 00000000..e2756421 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-list.c @@ -0,0 +1,329 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * ClapperEnhancerProxyList: + * + * A list of enhancer proxies. + * + * Since: 0.10 + */ + +#include + +#include "clapper.h" +#include "clapper-enhancer-proxy-list-private.h" +#include "clapper-enhancer-proxy-private.h" + +#define GST_CAT_DEFAULT clapper_enhancer_proxy_list_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperEnhancerProxyList +{ + GstObject parent; + + GPtrArray *proxies; +}; + +enum +{ + PROP_0, + PROP_N_PROXIES, + PROP_LAST +}; + +static void clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface); + +#define parent_class clapper_enhancer_proxy_list_parent_class +G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, GST_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_enhancer_proxy_list_model_iface_init)); + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static GType +clapper_enhancer_proxy_list_model_get_item_type (GListModel *model) +{ + return CLAPPER_TYPE_ENHANCER_PROXY; +} + +static guint +clapper_enhancer_proxy_list_model_get_n_items (GListModel *model) +{ + return CLAPPER_ENHANCER_PROXY_LIST_CAST (model)->proxies->len; +} + +static gpointer +clapper_enhancer_proxy_list_model_get_item (GListModel *model, guint index) +{ + ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (model); + ClapperEnhancerProxy *proxy = NULL; + + if (G_LIKELY (index < self->proxies->len)) + proxy = gst_object_ref (g_ptr_array_index (self->proxies, index)); + + return proxy; +} + +static void +clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = clapper_enhancer_proxy_list_model_get_item_type; + iface->get_n_items = clapper_enhancer_proxy_list_model_get_n_items; + iface->get_item = clapper_enhancer_proxy_list_model_get_item; +} + +/* + * clapper_enhancer_proxy_list_new_named: + * @name: (nullable): name of the #GstObject + * + * Returns: (transfer full): a new #ClapperEnhancerProxyList instance + */ +ClapperEnhancerProxyList * +clapper_enhancer_proxy_list_new_named (const gchar *name) +{ + ClapperEnhancerProxyList *list; + + list = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY_LIST, + "name", name, NULL); + gst_object_ref_sink (list); + + return list; +} + +void +clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *self, ClapperEnhancerProxy *proxy) +{ + g_ptr_array_add (self->proxies, proxy); + gst_object_set_parent (GST_OBJECT_CAST (proxy), GST_OBJECT_CAST (self)); +} + +/* + * clapper_enhancer_proxy_list_fill_from_global_proxies: + * + * Fill list with unconfigured proxies from global proxies list. + */ +void +clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *self) +{ + ClapperEnhancerProxyList *global_list = clapper_get_global_enhancer_proxies (); + static guint _list_id = 0; + guint i; + + for (i = 0; i < global_list->proxies->len; ++i) { + ClapperEnhancerProxy *proxy, *proxy_copy; + gchar obj_name[64]; + + proxy = clapper_enhancer_proxy_list_peek_proxy (global_list, i); + + /* Name newly created proxy, very useful for debugging. Keep index per + * list, so it will be the same as the player that proxy belongs to. */ + g_snprintf (obj_name, sizeof (obj_name), "%s-proxy%u", + clapper_enhancer_proxy_get_friendly_name (proxy), _list_id); + proxy_copy = clapper_enhancer_proxy_copy (proxy, obj_name); + + clapper_enhancer_proxy_list_take_proxy (self, proxy_copy); + } + _list_id++; +} + +static gint +_sort_values_by_name (ClapperEnhancerProxy *proxy_a, ClapperEnhancerProxy *proxy_b) +{ + return g_ascii_strcasecmp ( + clapper_enhancer_proxy_get_friendly_name (proxy_a), + clapper_enhancer_proxy_get_friendly_name (proxy_b)); +} + +/* + * clapper_enhancer_proxy_list_sort: + * + * Sort all list elements by enhancer friendly name. + */ +void +clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *self) +{ + g_ptr_array_sort_values (self->proxies, (GCompareFunc) _sort_values_by_name); +} + +/** + * clapper_enhancer_proxy_list_get_proxy: + * @list: a #ClapperEnhancerProxyList + * @index: an enhancer proxy index + * + * Get the #ClapperEnhancerProxy at index. + * + * This behaves the same as [method@Gio.ListModel.get_item], and is here + * for code uniformity and convenience to avoid type casting by user. + * + * Returns: (transfer full) (nullable): The #ClapperEnhancerProxy at @index. + * + * Since: 0.10 + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *self, guint index) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL); + + return g_list_model_get_item (G_LIST_MODEL (self), index); +} + +/** + * clapper_enhancer_proxy_list_peek_proxy: (skip) + * @list: a #ClapperEnhancerProxyList + * @index: an enhancer proxy index + * + * Get the #ClapperEnhancerProxy at index. + * + * Similar to [method@Clapper.EnhancerProxyList.get_proxy], but does not take + * a new reference on proxy. + * + * Proxies in a list are only removed when a [class@Clapper.Player] instance + * they originate from is destroyed, so do not use returned object afterwards + * unless you take an additional reference on it. + * + * Returns: (transfer none) (nullable): The #ClapperEnhancerProxy at @index. + * + * Since: 0.10 + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *self, guint index) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL); + + return g_ptr_array_index (self->proxies, index); +} + +/** + * clapper_enhancer_proxy_list_get_proxy_by_module: + * @list: a #ClapperEnhancerProxyList + * @module_name: an enhancer module name + * + * Get the #ClapperEnhancerProxy by module name as defined in its plugin file. + * + * A convenience function to find a #ClapperEnhancerProxy by its unique + * module name in the list. + * + * Returns: (transfer full) (nullable): The #ClapperEnhancerProxy with requested module name. + * + * Since: 0.10 + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *self, const gchar *module_name) +{ + guint i; + + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL); + g_return_val_if_fail (module_name != NULL, NULL); + + for (i = 0; i < self->proxies->len; ++i) { + ClapperEnhancerProxy *proxy = g_ptr_array_index (self->proxies, i); + + if (strcmp (clapper_enhancer_proxy_get_module_name (proxy), module_name) == 0) + return gst_object_ref (proxy); + } + + return NULL; +} + +/** + * clapper_enhancer_proxy_list_get_n_proxies: + * @list: a #ClapperEnhancerProxyList + * + * Get the number of proxies in #ClapperEnhancerProxyList. + * + * This behaves the same as [method@Gio.ListModel.get_n_items], and is here + * for code uniformity and convenience to avoid type casting by user. + * + * Returns: The number of proxies in #ClapperEnhancerProxyList. + * + * Since: 0.10 + */ +guint +clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), 0); + + return g_list_model_get_n_items (G_LIST_MODEL (self)); +} + +static void +_proxy_remove_func (ClapperEnhancerProxy *proxy) +{ + gst_object_unparent (GST_OBJECT_CAST (proxy)); + gst_object_unref (proxy); +} + +static void +clapper_enhancer_proxy_list_init (ClapperEnhancerProxyList *self) +{ + self->proxies = g_ptr_array_new_with_free_func ((GDestroyNotify) _proxy_remove_func); +} + +static void +clapper_enhancer_proxy_list_finalize (GObject *object) +{ + ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_ptr_array_unref (self->proxies); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_enhancer_proxy_list_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object); + + switch (prop_id) { + case PROP_N_PROXIES: + g_value_set_uint (value, clapper_enhancer_proxy_list_get_n_proxies (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_enhancer_proxy_list_class_init (ClapperEnhancerProxyListClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerproxylist", 0, + "Clapper Enhancer Proxy List"); + + gobject_class->get_property = clapper_enhancer_proxy_list_get_property; + gobject_class->finalize = clapper_enhancer_proxy_list_finalize; + + /** + * ClapperEnhancerProxyList:n-proxies: + * + * Number of proxies in the list. + * + * Since: 0.10 + */ + param_specs[PROP_N_PROXIES] = g_param_spec_uint ("n-proxies", + NULL, NULL, 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} diff --git a/src/lib/clapper/clapper-enhancer-proxy-list.h b/src/lib/clapper/clapper-enhancer-proxy-list.h new file mode 100644 index 00000000..eb810d2a --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-list.h @@ -0,0 +1,53 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_ENHANCER_PROXY_LIST (clapper_enhancer_proxy_list_get_type()) +#define CLAPPER_ENHANCER_PROXY_LIST_CAST(obj) ((ClapperEnhancerProxyList *)(obj)) + +CLAPPER_API +G_DECLARE_FINAL_TYPE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, CLAPPER, ENHANCER_PROXY_LIST, GstObject) + +CLAPPER_API +ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *list, guint index); + +CLAPPER_API +ClapperEnhancerProxy * clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *list, guint index); + +CLAPPER_API +ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *list, const gchar *module_name); + +CLAPPER_API +guint clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *list); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy-private.h b/src/lib/clapper/clapper-enhancer-proxy-private.h new file mode 100644 index 00000000..570d93d1 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy-private.h @@ -0,0 +1,47 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 +#include + +#include "clapper-enhancer-proxy.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +ClapperEnhancerProxy * clapper_enhancer_proxy_new_global_take (GObject *peas_info); // Using parent type for building without libpeas + +G_GNUC_INTERNAL +ClapperEnhancerProxy * clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_name); + +G_GNUC_INTERNAL +gboolean clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +gboolean clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *proxy, GObject *enhancer); + +G_GNUC_INTERNAL +GObject * clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *proxy); + +G_GNUC_INTERNAL +void clapper_enhancer_proxy_apply_current_config_to_enhancer (ClapperEnhancerProxy *proxy, GObject *enhancer); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy.c b/src/lib/clapper/clapper-enhancer-proxy.c new file mode 100644 index 00000000..a5f78d5a --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy.c @@ -0,0 +1,1035 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * ClapperEnhancerProxy: + * + * An intermediary between player and enhancer plugin. + * + * Applications can use this to inspect enhancer information, + * its properties and configure them. + * + * Clapper player manages all enhancers internally, including creating when + * needed and destroying them later. Instead, it provides access to so called + * enhancer proxy objects which allow to browse available enhancer properties + * and store their config either globally or locally for each player instance. + * + * Use [func@Clapper.get_global_enhancer_proxies] or [property@Clapper.Player:enhancer-proxies] + * property to access a [class@Clapper.EnhancerProxyList] of available enhancer proxies. While both + * lists include the same amount of proxies, the difference is which properties can be configured + * in which list. Only the latter allows tweaking of local (per player instance) properties using + * [method@Clapper.EnhancerProxy.set_locally] function. + * + * Since: 0.10 + */ + +#include "config.h" + +#include + +#include "clapper.h" +#include "clapper-enhancer-proxy-private.h" +#include "clapper-extractable.h" +#include "clapper-enums.h" + +#include "clapper-functionalities-availability.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include +#endif + +#define CONFIG_STRUCTURE_NAME "config" + +#define GST_CAT_DEFAULT clapper_enhancer_proxy_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperEnhancerProxy +{ + GstObject parent; + + /* Hold a ref on info to ensure + * below props are kept alive */ + GObject *peas_info; + + const gchar *friendly_name; + const gchar *module_name; + const gchar *module_dir; + const gchar *description; + const gchar *version; + + guint n_ifaces; + GType *ifaces; + + guint n_pspecs; + GParamSpec **pspecs; + + ClapperEnhancerParamFlags scope; + GstStructure *local_config; + + /* GSettings are not thread-safe, + * so store schema instead */ + GSettingsSchema *schema; + gboolean schema_init_done; +}; + +enum +{ + PROP_0, + PROP_FRIENDLY_NAME, + PROP_MODULE_NAME, + PROP_MODULE_DIR, + PROP_DESCRIPTION, + PROP_VERSION, + PROP_LAST +}; + +#define parent_class clapper_enhancer_proxy_parent_class +G_DEFINE_TYPE (ClapperEnhancerProxy, clapper_enhancer_proxy, GST_TYPE_OBJECT); + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static gboolean +_update_config_cb (GQuark field_id, const GValue *value, GstStructure *config) +{ + gst_structure_set_value (config, g_quark_to_string (field_id), value); + return TRUE; +} + +static void +_update_local_config_from_structure (ClapperEnhancerProxy *self, const GstStructure *src) +{ + GST_OBJECT_LOCK (self); + + if (!self->local_config) + self->local_config = gst_structure_copy (src); + else + gst_structure_foreach (src, (GstStructureForeachFunc) _update_config_cb, self->local_config); + + GST_OBJECT_UNLOCK (self); +} + +/* + * clapper_enhancer_proxy_new_global_take: + * @peas_info: (transfer full): a #PeasPluginInfo cast to GObject + * + * Create a new proxy. This should be only used for creating + * global proxies using @peas_info from enhancer loader, + * while player should use copies of global proxies. + * + * Returns: (transfer full): a new #ClapperEnhancerProxy instance. + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_new_global_take (GObject *peas_info) +{ + ClapperEnhancerProxy *proxy; + +#if CLAPPER_WITH_ENHANCERS_LOADER + const PeasPluginInfo *info = (const PeasPluginInfo *) peas_info; + gchar obj_name[64]; + const gchar *friendly_name; + + /* Name newly created proxy for easier debugging. Its best + * to do it with g_object_new(), as this avoid GStreamer + * naming it first with us renaming it afterwards. */ + friendly_name = peas_plugin_info_get_name (info); + g_snprintf (obj_name, sizeof (obj_name), "%s-global-proxy", friendly_name); + proxy = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY, "name", obj_name, NULL); + + proxy->peas_info = peas_info; + proxy->friendly_name = friendly_name; + proxy->module_name = peas_plugin_info_get_module_name (info); + proxy->module_dir = peas_plugin_info_get_module_dir (info); + proxy->description = peas_plugin_info_get_description (info); + proxy->version = peas_plugin_info_get_version (info); +#else + /* XXX: This should never be reached. We do not + * create proxies if we cannot load enhancers. */ + proxy = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY, NULL); +#endif + + proxy->scope = CLAPPER_ENHANCER_PARAM_GLOBAL; + + gst_object_ref_sink (proxy); + + return proxy; +} + +/* + * clapper_enhancer_proxy_copy: + * @src_proxy: a #ClapperEnhancerProxy to copy + * @copy_name: name of the #GstObject copy + * + * Create a copy of enhancer proxy. + * + * Using another proxy as source to avoids reading cache again. + * This is mainly for internal usage to create new unconfigured + * from global proxy list. + * + * Returns: (transfer full): a new #ClapperEnhancerProxy instance. + */ +ClapperEnhancerProxy * +clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_name) +{ + ClapperEnhancerProxy *copy; + guint i; + + copy = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY, + "name", copy_name, NULL); + + copy->peas_info = g_object_ref (src_proxy->peas_info); + copy->friendly_name = src_proxy->friendly_name; + copy->module_name = src_proxy->module_name; + copy->module_dir = src_proxy->module_dir; + copy->description = src_proxy->description; + copy->version = src_proxy->version; + + /* Copy extra data from source proxy */ + + copy->n_ifaces = src_proxy->n_ifaces; + copy->ifaces = g_new (GType, copy->n_ifaces); + + for (i = 0; i < src_proxy->n_ifaces; ++i) + copy->ifaces[i] = src_proxy->ifaces[i]; + + copy->n_pspecs = src_proxy->n_pspecs; + copy->pspecs = g_new (GParamSpec *, copy->n_pspecs); + + for (i = 0; i < src_proxy->n_pspecs; ++i) + copy->pspecs[i] = src_proxy->pspecs[i]; + + copy->scope = CLAPPER_ENHANCER_PARAM_LOCAL; + + GST_OBJECT_LOCK (src_proxy); + + if (src_proxy->schema) + copy->schema = g_settings_schema_ref (src_proxy->schema); + + copy->schema_init_done = src_proxy->schema_init_done; + + if (src_proxy->local_config) + copy->local_config = gst_structure_copy (src_proxy->local_config); + + GST_OBJECT_UNLOCK (src_proxy); + + gst_object_ref_sink (copy); + + return copy; +} + +static inline void +_init_schema (ClapperEnhancerProxy *self) +{ + GST_OBJECT_LOCK (self); + + if (self->schema_init_done) { + GST_OBJECT_UNLOCK (self); + return; + } + + if (self->scope == CLAPPER_ENHANCER_PARAM_GLOBAL) { + guint i; + gboolean configurable = FALSE; + + GST_TRACE_OBJECT (self, "Initializing settings schema"); + + /* Check whether to expect any schema without file query */ + for (i = 0; i < self->n_pspecs; ++i) { + if ((configurable = (self->pspecs[i]->flags & CLAPPER_ENHANCER_PARAM_GLOBAL))) + break; + } + + if (configurable) { + GSettingsSchemaSource *schema_source; + GError *error = NULL; + + schema_source = g_settings_schema_source_new_from_directory ( + self->module_dir, g_settings_schema_source_get_default (), TRUE, &error); + + if (!error) { + gchar **non_reloc = NULL; + + g_settings_schema_source_list_schemas (schema_source, + FALSE, &non_reloc, NULL); + + if (non_reloc && non_reloc[0]) { + const gchar *schema_id = non_reloc[0]; + + GST_DEBUG_OBJECT (self, "Found settings schema: %s", schema_id); + self->schema = g_settings_schema_source_lookup (schema_source, schema_id, FALSE); + } + + g_strfreev (non_reloc); + } else { + GST_ERROR_OBJECT (self, "Could not load settings, reason: %s", + GST_STR_NULL (error->message)); + g_error_free (error); + } + + /* SAFETY: Not sure if on error invalid source might be returned */ + g_clear_pointer (&schema_source, g_settings_schema_source_unref); + } + } else { + ClapperEnhancerProxyList *proxies; + ClapperEnhancerProxy *global_proxy; + + proxies = clapper_get_global_enhancer_proxies (); + global_proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, self->module_name); + + /* Must ensure init was done on global + * proxy before accessing its schema */ + _init_schema (global_proxy); + + /* Just ref the schema from global one, so we + * can avoid loading them in local proxies */ + if (global_proxy->schema) + self->schema = g_settings_schema_ref (global_proxy->schema); + + gst_object_unref (global_proxy); + } + + self->schema_init_done = TRUE; + + GST_OBJECT_UNLOCK (self); +} + +gboolean +clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *self) +{ + GST_FIXME_OBJECT (self, "Implement enhancer proxy caching"); + + return FALSE; +} + +gboolean +clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *self, GObject *enhancer) +{ + self->ifaces = g_type_interfaces (G_OBJECT_TYPE (enhancer), &self->n_ifaces); + self->pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (enhancer), &self->n_pspecs); + + GST_DEBUG_OBJECT (self, "Filled proxy \"%s\", n_ifaces: %u, n_pspecs: %u", + self->friendly_name, self->n_ifaces, self->n_pspecs); + + return TRUE; +} + +GObject * +clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *self) +{ + return self->peas_info; +} + +static gboolean +_apply_config_cb (GQuark field_id, const GValue *value, GObject *enhancer) +{ + g_object_set_property (enhancer, g_quark_to_string (field_id), value); + return TRUE; +} + +void +clapper_enhancer_proxy_apply_current_config_to_enhancer (ClapperEnhancerProxy *self, GObject *enhancer) +{ + GSettings *settings = clapper_enhancer_proxy_get_settings (self); + GstStructure *merged_config = NULL; + guint i; + + GST_DEBUG_OBJECT (self, "Applying config to enhancer"); + + /* Lock here to ensure consistent local config */ + GST_OBJECT_LOCK (self); + + for (i = 0; i < self->n_pspecs; ++i) { + GParamSpec *pspec = self->pspecs[i]; + + /* Using "has_field", as set value might be %NULL */ + if ((pspec->flags & CLAPPER_ENHANCER_PARAM_LOCAL) + && gst_structure_has_field (self->local_config, pspec->name)) { + if (!merged_config) + merged_config = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + gst_structure_set_value (merged_config, pspec->name, + gst_structure_get_value (self->local_config, pspec->name)); + continue; // local config overshadows global one + } + if (settings && (pspec->flags & CLAPPER_ENHANCER_PARAM_GLOBAL)) { + GVariant *val = g_settings_get_value (settings, pspec->name); + GVariant *def = g_settings_get_default_value (settings, pspec->name); + + if (!g_variant_equal (val, def)) { + if (!merged_config) + merged_config = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + switch (pspec->value_type) { + case G_TYPE_BOOLEAN: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_boolean (val), NULL); + break; + case G_TYPE_INT: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_int32 (val), NULL); + break; + case G_TYPE_UINT: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_uint32 (val), NULL); + break; + case G_TYPE_DOUBLE: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_double (val), NULL); + break; + case G_TYPE_STRING: + gst_structure_set (merged_config, pspec->name, + pspec->value_type, g_variant_get_string (val, NULL), NULL); + break; + default:{ + if (G_IS_PARAM_SPEC_ENUM (pspec)) { + gst_structure_set (merged_config, pspec->name, + G_TYPE_INT, g_variant_get_int32 (val), NULL); + break; + } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) { + gst_structure_set (merged_config, pspec->name, + G_TYPE_UINT, g_variant_get_uint32 (val), NULL); + break; + } + GST_ERROR_OBJECT (self, "Unsupported enhancer \"%s\" setting type: %s", + pspec->name, g_type_name (pspec->value_type)); + break; + } + } + } + + g_variant_unref (val); + g_variant_unref (def); + } + } + + GST_OBJECT_UNLOCK (self); + + g_clear_object (&settings); + + /* Nothing if no configurable properties + * or all have default values */ + if (merged_config) { + gst_structure_foreach (merged_config, (GstStructureForeachFunc) _apply_config_cb, enhancer); + gst_structure_free (merged_config); + } + + GST_DEBUG_OBJECT (self, "Enhancer config applied"); +} + +/** + * clapper_enhancer_proxy_get_friendly_name: + * @proxy: a #ClapperEnhancerProxy + * + * Get a name from enhancer plugin info file. + * Can be used for showing in UI and such. + * + * Name field in plugin info file is mandatory, + * so this function never returns %NULL. + * + * Returns: (not nullable): name of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_friendly_name (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->friendly_name; +} + +/** + * clapper_enhancer_proxy_get_module_name: + * @proxy: a #ClapperEnhancerProxy + * + * Get name of the module from enhancer plugin info file. + * This value is used to uniquely identify a particular plugin. + * + * Module name in plugin info file is mandatory, + * so this function never returns %NULL. + * + * Returns: (not nullable): name of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_module_name (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->module_name; +} + +/** + * clapper_enhancer_proxy_get_module_dir: + * @proxy: a #ClapperEnhancerProxy + * + * Get a path to the directory from which enhancer is loaded. + * + * Returns: (not nullable): installation directory of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_module_dir (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->module_dir; +} + +/** + * clapper_enhancer_proxy_get_description: + * @proxy: a #ClapperEnhancerProxy + * + * Get description from enhancer plugin info file. + * + * Returns: (nullable): description of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_description (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->description; +} + +/** + * clapper_enhancer_proxy_get_version: + * @proxy: a #ClapperEnhancerProxy + * + * Get version string from enhancer plugin info file. + * + * Returns: (nullable): version string of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *self) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + return self->version; +} + +/** + * clapper_enhancer_proxy_get_extra_data: + * @proxy: a #ClapperEnhancerProxy + * @key: name of the data to lookup + * + * Get extra data from enhancer plugin info file specified by @key. + * + * External data in the plugin info file is prefixed with `X-`. + * For example `X-Schemes=https`. + * + * Returns: (nullable): extra data value of the proxied enhancer. + * + * Since: 0.10 + */ +const gchar * +clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *self, const gchar *key) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + g_return_val_if_fail (key != NULL, NULL); + +#if CLAPPER_WITH_ENHANCERS_LOADER + return peas_plugin_info_get_external_data ((const PeasPluginInfo *) self->peas_info, key); +#else + return NULL; +#endif +} + +/** + * clapper_enhancer_proxy_extra_data_lists_value: + * @proxy: a #ClapperEnhancerProxy + * @key: name of the data to lookup + * @value: string to check for + * + * A convenience function to check whether @proxy plugin file + * has an extra data field with @key that among separated list + * of values includes @value (works on single value lists too). + * + * For example, when extra data in the plugin is `X-Schemes=https;http`, + * calling this function with "X-Schemes" as key and "http" as value will + * return %TRUE. + * + * Returns: whether list named with @key existed and contained @value. + * + * Since: 0.10 + */ +gboolean +clapper_enhancer_proxy_extra_data_lists_value (ClapperEnhancerProxy *self, + const gchar *key, const gchar *value) +{ + const gchar *list_str; + + /* Remaining things are checked in "get_extra_data()" */ + g_return_val_if_fail (value != NULL, FALSE); + + if ((list_str = clapper_enhancer_proxy_get_extra_data (self, key))) { + gsize value_len = strlen (value); + guint i = 0; + + while (list_str[i] != '\0') { + guint end = i; + + while (list_str[end] != ';' && list_str[end] != '\0') + ++end; + + /* Compare letters count until separator and prefix of whole string */ + if (end - i == value_len && g_str_has_prefix (list_str + i, value)) + return TRUE; + + i = end; + + /* Move to the next letter after ';' */ + if (list_str[i] != '\0') + ++i; + } + } + + return FALSE; +} + +/** + * clapper_enhancer_proxy_get_target_interfaces: + * @proxy: a #ClapperEnhancerProxy + * @n_interfaces: (out): return location for the length of the returned array + * + * Get an array of interfaces that target enhancer implements. + * + * The returned array includes only Clapper specific interfaces + * for writing enhancers. Applications should not care about any + * other interface types that given enhancer is using internally. + * + * Returns: (transfer none) (nullable) (array length=n_interfaces): an array of #GType interfaces. + * + * Since: 0.10 + */ +GType * +clapper_enhancer_proxy_get_target_interfaces (ClapperEnhancerProxy *self, guint *n_interfaces) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + if (n_interfaces) + *n_interfaces = self->n_ifaces; + + return self->ifaces; +} + +/** + * clapper_enhancer_proxy_target_has_interface: + * @proxy: a #ClapperEnhancerProxy + * @iface_type: an interface #GType + * + * A convenience function to check if target enhancer implements given interface. + * + * This works only with Clapper specific interfaces as @iface_type + * for writing enhancers. Applications should not care about any + * other interface types that given enhancer is using internally. + * + * Returns: whether target implements given interface. + * + * Since: 0.10 + */ +gboolean +clapper_enhancer_proxy_target_has_interface (ClapperEnhancerProxy *self, GType iface_type) +{ + guint i; + + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), FALSE); + + for (i = 0; i < self->n_ifaces; ++i) { + if (self->ifaces[i] == iface_type) + return TRUE; + } + + return FALSE; +} + +/** + * clapper_enhancer_proxy_get_target_properties: + * @proxy: a #ClapperEnhancerProxy + * @n_properties: (out): return location for the length of the returned array + * + * Get an array of properties in target enhancer. + * + * Implementations can use this in order to find out what properties, type of + * their values (including valid ranges) are allowed to set for a given enhancer. + * + * Use [flags@Clapper.EnhancerParamFlags] against flags of given [class@GObject.ParamSpec] + * to find out whether they are local, global or neither of them (internal). + * + * The returned array includes only Clapper enhancer specific properties (global and local). + * Applications can not access any other properties that given enhancer is using internally. + * + * Returns: (transfer none) (nullable) (array length=n_properties): an array of #GParamSpec objects. + * + * Since: 0.10 + */ +GParamSpec ** +clapper_enhancer_proxy_get_target_properties (ClapperEnhancerProxy *self, guint *n_properties) +{ + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + if (n_properties) + *n_properties = self->n_pspecs; + + return self->pspecs; +} + +/** + * clapper_enhancer_proxy_get_settings: + * @proxy: a #ClapperEnhancerProxy + * + * Get #GSettings of an enhancer. + * + * Implementations can use this together with [method@Clapper.EnhancerProxy.get_target_properties] + * in order to allow user to configure global enhancer properties. + * + * Settings include only keys from properties with [flags@Clapper.EnhancerParamFlags.GLOBAL] + * flag and are meant ONLY for user to set. To configure application local enhancer properties, + * use [method@Clapper.EnhancerProxy.set_locally] instead. + * + * This function returns a new instance of #GSettings, so settings can be accessed + * from different threads if needed. + * + * Returns: (transfer full) (nullable): A new #GSettings instance for an enhancer. + * + * Since: 0.10 + */ +GSettings * +clapper_enhancer_proxy_get_settings (ClapperEnhancerProxy *self) +{ + GSettings *settings = NULL; + + g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY (self), NULL); + + /* Try to lazy load schemas */ + _init_schema (self); + + if (self->schema) + settings = g_settings_new_full (self->schema, NULL, NULL); + + return settings; +} + +static GParamSpec * +_find_target_pspec_by_name (ClapperEnhancerProxy *self, const gchar *name) +{ + guint i; + + name = g_intern_string (name); + for (i = 0; i < self->n_pspecs; ++i) { + GParamSpec *pspec = self->pspecs[i]; + + /* GParamSpec names are always interned */ + if (pspec->name == name) + return pspec; + } + + g_warning ("No property \"%s\" in target of \"%s\" (%s)", + name, self->friendly_name, self->module_name); + + return NULL; +} + +static gboolean +_structure_take_value_by_pspec (ClapperEnhancerProxy *self, + GstStructure *structure, GParamSpec *pspec, GValue *value) +{ + if (G_LIKELY (G_VALUE_TYPE (value) == pspec->value_type) + && !g_param_value_validate (pspec, value)) { + if (G_LIKELY (pspec->flags & self->scope)) { + gst_structure_take_value (structure, pspec->name, value); + return TRUE; + } else { + g_warning ("Trying to set \"%s\" (%s) target property \"%s\" that is outside of proxy %s scope", + self->friendly_name, self->module_name, pspec->name, + (self->scope == CLAPPER_ENHANCER_PARAM_GLOBAL) ? "GLOBAL" : "LOCAL"); + } + } else { + g_warning ("Wrong value type or out of range for \"%s\" (%s) target property \"%s\"", + self->friendly_name, self->module_name, pspec->name); + } + + return FALSE; +} + +/** + * clapper_enhancer_proxy_set_locally: + * @proxy: a #ClapperEnhancerProxy + * @first_property_name: name of the first property to configure + * @...: %NULL-terminated list of arguments + * + * Configure one or more properties which have [flags@Clapper.EnhancerParamFlags.LOCAL] + * flag set on the target enhancer instance. + * + * Implementations can use this together with [method@Clapper.EnhancerProxy.get_target_properties] + * in order to configure local enhancer properties. + * + * Arguments should be %NULL terminated list of property name and value to set + * (like [method@GObject.Object.set] arguments). + * + * Since: 0.10 + */ +void +clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *self, const gchar *first_property_name, ...) +{ + GstStructure *structure; + const gchar *name; + va_list args; + + g_return_if_fail (CLAPPER_IS_ENHANCER_PROXY (self)); + g_return_if_fail (first_property_name != NULL); + + if (G_UNLIKELY (self->scope != CLAPPER_ENHANCER_PARAM_LOCAL)) { + g_warning ("Trying to apply local config to a non-local enhancer proxy!"); + return; + } + + structure = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + va_start (args, first_property_name); + name = first_property_name; + + while (name) { + GParamSpec *pspec = _find_target_pspec_by_name (self, name); + + if (G_LIKELY (pspec != NULL)) { + GValue value = G_VALUE_INIT; + gchar *err = NULL; + + G_VALUE_COLLECT_INIT (&value, pspec->value_type, args, 0, &err); + if (G_UNLIKELY (err != NULL)) { + g_critical ("%s", err); + g_free (err); + g_value_unset (&value); + break; + } + + if (!_structure_take_value_by_pspec (self, structure, pspec, &value)) + g_value_unset (&value); + } else { + break; + } + + name = va_arg (args, const gchar *); + } + + va_end (args); + + if (G_UNLIKELY (gst_structure_n_fields (structure) == 0)) { + gst_structure_free (structure); + return; + } + + _update_local_config_from_structure (self, structure); + + /* TODO: _post_local_config instead of free if managed + * (for when managed interfaces are implemented) */ + + gst_structure_free (structure); +} + +/** + * clapper_enhancer_proxy_set_locally_with_table: (rename-to clapper_enhancer_proxy_set_locally) + * @proxy: a #ClapperEnhancerProxy + * @table: (transfer none) (element-type utf8 GObject.Value): a #GHashTable with property names and values + * + * Same as [method@Clapper.EnhancerProxy.set_locally], but to configure uses + * [struct@GLib.HashTable] with string keys and [struct@GObject.Value] as their values. + * + * Since: 0.10 + */ +void +clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *self, GHashTable *table) +{ + GstStructure *structure; + GHashTableIter iter; + gpointer key_ptr, val_ptr; + + g_return_if_fail (CLAPPER_IS_ENHANCER_PROXY (self)); + g_return_if_fail (table != NULL); + + if (G_UNLIKELY (self->scope != CLAPPER_ENHANCER_PARAM_LOCAL)) { + g_warning ("Trying to apply local config to a non-local enhancer proxy!"); + return; + } + + structure = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + g_hash_table_iter_init (&iter, table); + while (g_hash_table_iter_next (&iter, &key_ptr, &val_ptr)) { + const gchar *name = (const gchar *) key_ptr; + GParamSpec *pspec = _find_target_pspec_by_name (self, name); + + if (G_LIKELY (pspec != NULL)) { + GValue value_copy = G_VALUE_INIT; + + if (val_ptr) { + const GValue *value = (const GValue *) val_ptr; + g_value_init (&value_copy, G_VALUE_TYPE (value)); + g_value_copy (value, &value_copy); + } else { // when setting property to NULL + if (pspec->value_type == G_TYPE_STRING) { + g_value_init (&value_copy, G_TYPE_STRING); + g_value_set_string (&value_copy, NULL); + } else { + g_value_init (&value_copy, G_TYPE_POINTER); + g_value_set_pointer (&value_copy, NULL); + } + } + + if (!_structure_take_value_by_pspec (self, structure, pspec, &value_copy)) + g_value_unset (&value_copy); + } + } + + if (G_UNLIKELY (gst_structure_n_fields (structure) == 0)) { + gst_structure_free (structure); + return; + } + + _update_local_config_from_structure (self, structure); + + /* TODO: _post_local_config instead of free if managed + * (for when managed interfaces are implemented) */ + + gst_structure_free (structure); +} + +static void +clapper_enhancer_proxy_init (ClapperEnhancerProxy *self) +{ +} + +static void +clapper_enhancer_proxy_finalize (GObject *object) +{ + ClapperEnhancerProxy *self = CLAPPER_ENHANCER_PROXY_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_object_unref (self->peas_info); + g_free (self->ifaces); + g_free (self->pspecs); + g_clear_pointer (&self->schema, g_settings_schema_unref); + gst_clear_structure (&self->local_config); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_enhancer_proxy_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperEnhancerProxy *self = CLAPPER_ENHANCER_PROXY_CAST (object); + + switch (prop_id) { + case PROP_FRIENDLY_NAME: + g_value_set_string (value, clapper_enhancer_proxy_get_friendly_name (self)); + break; + case PROP_MODULE_NAME: + g_value_set_string (value, clapper_enhancer_proxy_get_module_name (self)); + break; + case PROP_MODULE_DIR: + g_value_set_string (value, clapper_enhancer_proxy_get_module_dir (self)); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, clapper_enhancer_proxy_get_description (self)); + break; + case PROP_VERSION: + g_value_set_string (value, clapper_enhancer_proxy_get_version (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_enhancer_proxy_class_init (ClapperEnhancerProxyClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerproxy", 0, + "Clapper Enhancer Proxy"); + + gobject_class->get_property = clapper_enhancer_proxy_get_property; + gobject_class->finalize = clapper_enhancer_proxy_finalize; + + /** + * ClapperEnhancerProxy:friendly-name: + * + * Name from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_FRIENDLY_NAME] = g_param_spec_string ("friendly-name", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:module-name: + * + * Module name from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_MODULE_NAME] = g_param_spec_string ("module-name", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:module-dir: + * + * Module directory. + * + * Since: 0.10 + */ + param_specs[PROP_MODULE_DIR] = g_param_spec_string ("module-dir", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:description: + * + * Description from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_DESCRIPTION] = g_param_spec_string ("description", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperEnhancerProxy:version: + * + * Version from enhancer plugin info file. + * + * Since: 0.10 + */ + param_specs[PROP_VERSION] = g_param_spec_string ("version", + NULL, NULL, NULL, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} diff --git a/src/lib/clapper/clapper-enhancer-proxy.h b/src/lib/clapper/clapper-enhancer-proxy.h new file mode 100644 index 00000000..8ea32870 --- /dev/null +++ b/src/lib/clapper/clapper-enhancer-proxy.h @@ -0,0 +1,80 @@ +/* Clapper Playback Library + * Copyright (C) 2025 Rafał Dzięgiel + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 + +#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_ENHANCER_PROXY (clapper_enhancer_proxy_get_type()) +#define CLAPPER_ENHANCER_PROXY_CAST(obj) ((ClapperEnhancerProxy *)(obj)) + +CLAPPER_API +G_DECLARE_FINAL_TYPE (ClapperEnhancerProxy, clapper_enhancer_proxy, CLAPPER, ENHANCER_PROXY, GstObject) + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_friendly_name (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_module_name (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_module_dir (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_description (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *proxy); + +CLAPPER_API +const gchar * clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *proxy, const gchar *key); + +CLAPPER_API +gboolean clapper_enhancer_proxy_extra_data_lists_value (ClapperEnhancerProxy *proxy, const gchar *key, const gchar *value); + +CLAPPER_API +GType * clapper_enhancer_proxy_get_target_interfaces (ClapperEnhancerProxy *proxy, guint *n_interfaces); + +CLAPPER_API +gboolean clapper_enhancer_proxy_target_has_interface (ClapperEnhancerProxy *proxy, GType iface_type); + +CLAPPER_API +GParamSpec ** clapper_enhancer_proxy_get_target_properties (ClapperEnhancerProxy *proxy, guint *n_properties); + +CLAPPER_API +GSettings * clapper_enhancer_proxy_get_settings (ClapperEnhancerProxy *proxy); + +CLAPPER_API +void clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *proxy, const gchar *first_property_name, ...) G_GNUC_NULL_TERMINATED; + +CLAPPER_API +void clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *proxy, GHashTable *table); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancers-loader-private.h b/src/lib/clapper/clapper-enhancers-loader-private.h index c2a50d9c..7d7070de 100644 --- a/src/lib/clapper/clapper-enhancers-loader-private.h +++ b/src/lib/clapper/clapper-enhancers-loader-private.h @@ -22,21 +22,15 @@ #include #include +#include "clapper-enhancer-proxy-list.h" +#include "clapper-enhancer-proxy.h" + G_BEGIN_DECLS G_GNUC_INTERNAL -void clapper_enhancers_loader_initialize (void); +void clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies); G_GNUC_INTERNAL -gboolean clapper_enhancers_loader_has_enhancers (GType iface_type); - -G_GNUC_INTERNAL -gchar ** clapper_enhancers_loader_get_schemes (GType iface_type); - -G_GNUC_INTERNAL -gboolean clapper_enhancers_loader_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); - -G_GNUC_INTERNAL -GObject * clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri); +GObject * clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type); G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c index 663cbb3b..f5ed49c0 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -28,11 +28,13 @@ static HMODULE _enhancers_dll_handle = NULL; #endif #include "clapper-enhancers-loader-private.h" +#include "clapper-enhancer-proxy-list-private.h" +#include "clapper-enhancer-proxy-private.h" + +// Supported interfaces +#include "clapper-extractable.h" #define ENHANCER_INTERFACES "X-Interfaces" -#define ENHANCER_SCHEMES "X-Schemes" -#define ENHANCER_HOSTS "X-Hosts" -#define ENHANCER_IFACE_NAME_FROM_TYPE(type) (g_type_name (type) + 7) // strip "Clapper" prefix #define GST_CAT_DEFAULT clapper_enhancers_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -58,10 +60,11 @@ _import_enhancers (const gchar *enhancers_path) * Initializes #PeasEngine with directories that store enhancers. */ void -clapper_enhancers_loader_initialize (void) +clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) { const gchar *enhancers_path; gchar *custom_path = NULL; + guint i, n_items; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0, "Clapper Enhancer Loader"); @@ -104,321 +107,83 @@ clapper_enhancers_loader_initialize (void) _import_enhancers (enhancers_path); } - if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_INFO) { - GListModel *list = (GListModel *) _engine; - guint i, n_items = g_list_model_get_n_items (list); + n_items = g_list_model_get_n_items ((GListModel *) _engine); + for (i = 0; i < n_items; ++i) { + PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item ((GListModel *) _engine, i); + ClapperEnhancerProxy *proxy; + gboolean filled; - for (i = 0; i < n_items; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - GST_INFO ("Found enhancer: %s (%s)", peas_plugin_info_get_name (info), - peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES)); - g_object_unref (info); + /* Clapper supports only 1 proxy per plugin. Each plugin can + * ship 1 class, but it can implement more than 1 interface. */ + proxy = clapper_enhancer_proxy_new_global_take ((GObject *) info); + + /* Try to fill missing data from cache (fast). + * Otherwise make an instance and fill missing data from it (slow). */ + if (!(filled = clapper_enhancer_proxy_fill_from_cache (proxy))) { + GObject *enhancer; + GType main_types[1] = { CLAPPER_TYPE_EXTRACTABLE }; + guint j; + + /* We cannot ask libpeas for "any" of our main interfaces, so try each one until found */ + for (j = 0; j < G_N_ELEMENTS (main_types); ++j) { + if ((enhancer = clapper_enhancers_loader_create_enhancer (proxy, main_types[j]))) { + filled = clapper_enhancer_proxy_fill_from_instance (proxy, enhancer); + g_object_unref (enhancer); + + GST_FIXME_OBJECT (proxy, "Save enhancer proxy data to cache"); + break; + } + } } - GST_INFO ("Clapper enhancers initialized, found: %u", n_items); + if (G_LIKELY (filled)) { + GST_INFO ("Found enhancer: %s (%s)", clapper_enhancer_proxy_get_friendly_name (proxy), + clapper_enhancer_proxy_get_extra_data (proxy, ENHANCER_INTERFACES)); + clapper_enhancer_proxy_list_take_proxy (proxies, proxy); + } else { + GST_WARNING ("Enhancer \"%s\" init failed, skipping it", + clapper_enhancer_proxy_get_friendly_name (proxy)); + gst_object_unref (proxy); + } } + clapper_enhancer_proxy_list_sort (proxies); + + GST_INFO ("Clapper enhancers initialized, found: %u", + clapper_enhancer_proxy_list_get_n_proxies (proxies)); + g_free (custom_path); } -static inline gboolean -_is_name_listed (const gchar *name, const gchar *list_str) -{ - gsize name_len = strlen (name); - guint i = 0; - - while (list_str[i] != '\0') { - guint end = i; - - while (list_str[end] != ';' && list_str[end] != '\0') - ++end; - - /* Compare letters count until separator and prefix of whole string */ - if (end - i == name_len && g_str_has_prefix (list_str + i, name)) - return TRUE; - - i = end; - - /* Move to the next letter after ';' */ - if (list_str[i] != '\0') - ++i; - } - - return FALSE; -} - /* - * clapper_enhancers_loader_get_info: - * @iface_type: an interface #GType - * @scheme: an URI scheme - * @host: (nullable): an URI host - * - * Returns: (transfer full) (nullable): available #PeasPluginInfo or %NULL. - */ -static PeasPluginInfo * -clapper_enhancers_loader_get_info (GType iface_type, const gchar *scheme, const gchar *host) -{ - GListModel *list = (GListModel *) _engine; - PeasPluginInfo *found_info = NULL; - guint i, n_plugins = g_list_model_get_n_items (list); - const gchar *iface_name; - gboolean is_https; - - if (n_plugins == 0) { - GST_INFO ("No Clapper enhancers found"); - return NULL; - } - - iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); - - /* Strip common subdomains, so plugins do not - * have to list all combinations */ - if (host) { - if (g_str_has_prefix (host, "www.")) - host += 4; - else if (g_str_has_prefix (host, "m.")) - host += 2; - } - - GST_INFO ("Enhancer check, iface: %s, scheme: %s, host: %s", - iface_name, scheme, GST_STR_NULL (host)); - - is_https = (g_str_has_prefix (scheme, "http") - && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))); - - if (!host && is_https) - return NULL; - - for (i = 0; i < n_plugins; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - const gchar *iface_names, *schemes, *hosts; - - if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { - GST_DEBUG ("Skipping enhancer without interfaces: %s", peas_plugin_info_get_name (info)); - g_object_unref (info); - continue; - } - if (!_is_name_listed (iface_name, iface_names)) { - g_object_unref (info); - continue; - } - if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) { - GST_DEBUG ("Skipping enhancer without schemes: %s", peas_plugin_info_get_name (info)); - g_object_unref (info); - continue; - } - if (!_is_name_listed (scheme, schemes)) { - g_object_unref (info); - continue; - } - if (is_https) { - if (!(hosts = peas_plugin_info_get_external_data (info, ENHANCER_HOSTS))) { - GST_DEBUG ("Skipping enhancer without hosts: %s", peas_plugin_info_get_name (info)); - g_object_unref (info); - continue; - } - if (!_is_name_listed (host, hosts)) { - g_object_unref (info); - continue; - } - } - - found_info = info; - break; - } - - return found_info; -} - -/* - * clapper_enhancers_loader_has_enhancers: - * @iface_type: an interface #GType - * - * Check if any enhancer implementing given interface type is available. - * - * Returns: whether any valid enhancer was found. - */ -gboolean -clapper_enhancers_loader_has_enhancers (GType iface_type) -{ - GListModel *list = (GListModel *) _engine; - const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); - guint i, n_plugins; - - GST_DEBUG ("Checking for any enhancers of type: \"%s\"", iface_name); - - n_plugins = g_list_model_get_n_items (list); - - for (i = 0; i < n_plugins; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - const gchar *iface_names; - - if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { - g_object_unref (info); - continue; - } - if (!_is_name_listed (iface_name, iface_names)) { - g_object_unref (info); - continue; - } - - /* Additional validation */ - if (!peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES) - || !peas_plugin_info_get_external_data (info, ENHANCER_HOSTS)) { - g_object_unref (info); - continue; - } - - GST_DEBUG ("Found valid enhancers of type: \"%s\"", iface_name); - g_object_unref (info); - - return TRUE; - } - - GST_DEBUG ("No available enhancers of type: \"%s\"", iface_name); - - return FALSE; -} - -/* - * clapper_enhancers_loader_get_schemes: - * @iface_type: an interface #GType - * - * Get all supported schemes for a given interface type. - * The returned array consists of unique strings (no duplicates). - * - * Returns: (transfer full): all supported schemes by enhancers of type. - */ -gchar ** -clapper_enhancers_loader_get_schemes (GType iface_type) -{ - GListModel *list = (GListModel *) _engine; - GSList *found_schemes = NULL, *fs; - const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type); - gchar **schemes_strv; - guint i, n_plugins, n_schemes; - - GST_DEBUG ("Checking supported URI schemes for \"%s\"", iface_name); - - n_plugins = g_list_model_get_n_items (list); - - for (i = 0; i < n_plugins; ++i) { - PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i); - const gchar *iface_names, *schemes; - gchar **tmp_strv; - gint j; - - if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) { - g_object_unref (info); - continue; - } - if (!_is_name_listed (iface_name, iface_names)) { - g_object_unref (info); - continue; - } - if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) { - g_object_unref (info); - continue; - } - - tmp_strv = g_strsplit (schemes, ";", 0); - - for (j = 0; tmp_strv[j]; ++j) { - const gchar *scheme = tmp_strv[j]; - - if (!found_schemes || !g_slist_find_custom (found_schemes, - scheme, (GCompareFunc) strcmp)) { - found_schemes = g_slist_append (found_schemes, g_strdup (scheme)); - GST_INFO ("Found supported URI scheme: %s", scheme); - } - } - - g_strfreev (tmp_strv); - g_object_unref (info); - } - - n_schemes = g_slist_length (found_schemes); - schemes_strv = g_new0 (gchar *, n_schemes + 1); - - fs = found_schemes; - for (i = 0; i < n_schemes; ++i) { - schemes_strv[i] = fs->data; - fs = fs->next; - } - - GST_DEBUG ("Total found URI schemes: %u", n_schemes); - - /* Since string pointers were taken, - * free list without content */ - g_slist_free (found_schemes); - - return schemes_strv; -} - -/* - * clapper_enhancers_loader_check: + * clapper_enhancers_loader_create_enhancer: * @iface_type: a requested #GType - * @scheme: an URI scheme - * @host: (nullable): an URI host - * @name: (out) (optional) (transfer none): return location for found enhancer name + * @info: a #PeasPluginInfo * - * Checks if any enhancer can handle @uri without initializing loader - * or creating enhancer instance, thus this can be used freely from any thread. - * - * Returns: whether enhancer for given scheme and host is available. - */ -gboolean -clapper_enhancers_loader_check (GType iface_type, - const gchar *scheme, const gchar *host, const gchar **name) -{ - PeasPluginInfo *info; - - if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { - if (name) - *name = peas_plugin_info_get_name (info); - - g_object_unref (info); - - return TRUE; - } - - return FALSE; -} - -/* - * clapper_enhancers_loader_create_enhancer_for_uri: - * @iface_type: a requested #GType - * @uri: a #GUri - * - * Creates a new enhancer object for given URI. + * Creates a new enhancer object using @info. * * Enhancer should only be created and used within single thread. * * Returns: (transfer full) (nullable): a new enhancer instance. */ GObject * -clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri) +clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type) { GObject *enhancer = NULL; - PeasPluginInfo *info; - const gchar *scheme = g_uri_get_scheme (uri); - const gchar *host = g_uri_get_host (uri); + PeasPluginInfo *info = (PeasPluginInfo *) clapper_enhancer_proxy_get_peas_info (proxy); - if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { - g_mutex_lock (&load_lock); + g_mutex_lock (&load_lock); - if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) { - GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_name (info)); - } else if (!peas_engine_provides_extension (_engine, info, iface_type)) { - GST_ERROR ("No \"%s\" enhancer in plugin: %s", ENHANCER_IFACE_NAME_FROM_TYPE (iface_type), - peas_plugin_info_get_name (info)); - } else { - enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL); - } - - g_mutex_unlock (&load_lock); - g_object_unref (info); + if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) { + GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_module_name (info)); + } else if (!peas_engine_provides_extension (_engine, info, iface_type)) { + GST_LOG ("No \"%s\" enhancer in module: %s", g_type_name (iface_type), + peas_plugin_info_get_module_name (info)); + } else { + enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL); } + g_mutex_unlock (&load_lock); + return enhancer; } diff --git a/src/lib/clapper/clapper-enums.h b/src/lib/clapper/clapper-enums.h index a1576fcd..70dc5841 100644 --- a/src/lib/clapper/clapper-enums.h +++ b/src/lib/clapper/clapper-enums.h @@ -127,4 +127,28 @@ typedef enum CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT, } ClapperDiscovererDiscoveryMode; +/* NOTE: GStreamer uses param flags 8-16, so start with 17. */ +/** + * ClapperEnhancerParamFlags: + * @CLAPPER_ENHANCER_PARAM_GLOBAL: Use this flag for enhancer properties that should have global access scope. + * Such are meant for application `USER` to configure. + * @CLAPPER_ENHANCER_PARAM_LOCAL: Use this flag for enhancer properties that should have local access scope. + * Such are meant for `APPLICATION` to configure. + * @CLAPPER_ENHANCER_PARAM_FILEPATH: Use this flag for enhancer properties that store string with a file path. + * Applications can use this as a hint to show file selection instead of a text entry. + * @CLAPPER_ENHANCER_PARAM_DIRPATH: Use this flag for enhancer properties that store string with a directory path. + * Applications can use this as a hint to show directory selection instead of a text entry. + * + * Additional [flags@GObject.ParamFlags] to be set in enhancer plugins implementations. + * + * Since: 0.10 + */ +typedef enum +{ + CLAPPER_ENHANCER_PARAM_GLOBAL = 1 << 17, + CLAPPER_ENHANCER_PARAM_LOCAL = 1 << 18, + CLAPPER_ENHANCER_PARAM_FILEPATH = 1 << 19, + CLAPPER_ENHANCER_PARAM_DIRPATH = 1 << 20, +} ClapperEnhancerParamFlags; + G_END_DECLS diff --git a/src/lib/clapper/clapper-player-private.h b/src/lib/clapper/clapper-player-private.h index 21ef21a9..c042cbae 100644 --- a/src/lib/clapper/clapper-player-private.h +++ b/src/lib/clapper/clapper-player-private.h @@ -47,6 +47,8 @@ struct _ClapperPlayer ClapperFeaturesManager *features_manager; gint have_features; // atomic integer + ClapperEnhancerProxyList *enhancer_proxies; + /* This is different from queue current item as it is used/changed only * on player thread, so we can always update correct item without lock */ ClapperMediaItem *played_item; diff --git a/src/lib/clapper/clapper-player.c b/src/lib/clapper/clapper-player.c index e394b839..c84a10fd 100644 --- a/src/lib/clapper/clapper-player.c +++ b/src/lib/clapper/clapper-player.c @@ -49,6 +49,7 @@ #include "clapper-video-stream-private.h" #include "clapper-audio-stream-private.h" #include "clapper-subtitle-stream-private.h" +#include "clapper-enhancer-proxy-list-private.h" #include "clapper-enums-private.h" #include "clapper-utils-private.h" #include "../shared/clapper-shared-utils-private.h" @@ -77,6 +78,7 @@ enum PROP_VIDEO_STREAMS, PROP_AUDIO_STREAMS, PROP_SUBTITLE_STREAMS, + PROP_ENHANCER_PROXIES, PROP_AUTOPLAY, PROP_POSITION, PROP_SPEED, @@ -814,7 +816,11 @@ _element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self factory_name = g_intern_static_string (GST_OBJECT_NAME (factory)); GST_INFO_OBJECT (self, "Element setup: %s", factory_name); - if (factory_name == g_intern_static_string ("downloadbuffer")) { + if (factory_name == g_intern_static_string ("clapperenhancersrc")) { + g_object_set (element, + "enhancer-proxies", self->enhancer_proxies, + NULL); + } else if (factory_name == g_intern_static_string ("downloadbuffer")) { gchar *download_template; /* Only set props if we have download template */ @@ -1129,6 +1135,24 @@ clapper_player_get_subtitle_streams (ClapperPlayer *self) return self->subtitle_streams; } +/** + * clapper_player_get_enhancer_proxies: + * @player: a #ClapperPlayer + * + * Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects. + * + * Returns: (transfer none): a #ClapperEnhancerProxyList of enhancer proxies. + * + * Since: 0.10 + */ +ClapperEnhancerProxyList * +clapper_player_get_enhancer_proxies (ClapperPlayer *self) +{ + g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); + + return self->enhancer_proxies; +} + /** * clapper_player_set_autoplay: * @player: a #ClapperPlayer @@ -2297,6 +2321,11 @@ clapper_player_init (ClapperPlayer *self) self->subtitle_streams = clapper_stream_list_new (); gst_object_set_parent (GST_OBJECT_CAST (self->subtitle_streams), GST_OBJECT_CAST (self)); + self->enhancer_proxies = clapper_enhancer_proxy_list_new_named (NULL); + gst_object_set_parent (GST_OBJECT_CAST (self->enhancer_proxies), GST_OBJECT_CAST (self)); + + clapper_enhancer_proxy_list_fill_from_global_proxies (self->enhancer_proxies); + self->position_query = gst_query_new_position (GST_FORMAT_TIME); self->current_state = GST_STATE_NULL; @@ -2363,6 +2392,9 @@ clapper_player_finalize (GObject *object) gst_object_unparent (GST_OBJECT_CAST (self->subtitle_streams)); gst_object_unref (self->subtitle_streams); + gst_object_unparent (GST_OBJECT_CAST (self->enhancer_proxies)); + gst_object_unref (self->enhancer_proxies); + gst_query_unref (self->position_query); gst_clear_object (&self->collection); @@ -2394,6 +2426,9 @@ clapper_player_get_property (GObject *object, guint prop_id, case PROP_SUBTITLE_STREAMS: g_value_set_object (value, clapper_player_get_subtitle_streams (self)); break; + case PROP_ENHANCER_PROXIES: + g_value_set_object (value, clapper_player_get_enhancer_proxies (self)); + break; case PROP_AUTOPLAY: g_value_set_boolean (value, clapper_player_get_autoplay (self)); break; @@ -2593,6 +2628,20 @@ clapper_player_class_init (ClapperPlayerClass *klass) NULL, NULL, CLAPPER_TYPE_STREAM_LIST, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * ClapperPlayer:enhancer-proxies: + * + * List of available enhancers in the form of [class@Clapper.EnhancerProxy] objects. + * + * Use these to inspect available enhancers on the system and configure + * their properties on a per player instance basis. + * + * Since: 0.10 + */ + param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies", + NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * ClapperPlayer:autoplay: * diff --git a/src/lib/clapper/clapper-player.h b/src/lib/clapper/clapper-player.h index 48547168..c9c2f370 100644 --- a/src/lib/clapper/clapper-player.h +++ b/src/lib/clapper/clapper-player.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,9 @@ ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *player); CLAPPER_API ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *player); +CLAPPER_API +ClapperEnhancerProxyList * clapper_player_get_enhancer_proxies (ClapperPlayer *player); + CLAPPER_API void clapper_player_set_autoplay (ClapperPlayer *player, gboolean enabled); diff --git a/src/lib/clapper/clapper.c b/src/lib/clapper/clapper.c index dfc50955..255d2a0f 100644 --- a/src/lib/clapper/clapper.c +++ b/src/lib/clapper/clapper.c @@ -27,12 +27,14 @@ #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-features-bus-private.h" +#include "clapper-enhancer-proxy-list-private.h" #include "gst/clapper-plugin-private.h" #if CLAPPER_WITH_ENHANCERS_LOADER #include "clapper-enhancers-loader-private.h" #endif +static ClapperEnhancerProxyList *_proxies = NULL; static gboolean is_initialized = FALSE; static GMutex init_lock; @@ -51,8 +53,10 @@ clapper_init_check_internal (int *argc, char **argv[]) clapper_app_bus_initialize (); clapper_features_bus_initialize (); + _proxies = clapper_enhancer_proxy_list_new_named ("global-proxy-list"); + #if CLAPPER_WITH_ENHANCERS_LOADER - clapper_enhancers_loader_initialize (); + clapper_enhancers_loader_initialize (_proxies); #endif gst_plugin_register_static ( @@ -149,18 +153,75 @@ clapper_init_check (int *argc, char **argv[]) * Returns: whether a plausible enhancer was found. * * Since: 0.8 + * + * Deprecated: 0.10: Use list of enhancer proxies from [func@Clapper.get_global_enhancer_proxies] or + * [property@Clapper.Player:enhancer-proxies] and check if any proxy matches your search criteria. */ gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name) { - gboolean success = FALSE; + gboolean is_https; + guint i, n_proxies; g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_type), FALSE); g_return_val_if_fail (scheme != NULL, FALSE); -#if CLAPPER_WITH_ENHANCERS_LOADER - success = clapper_enhancers_loader_check (iface_type, scheme, host, name); -#endif + if (host) { + /* Strip common subdomains, so plugins do not + * have to list all combinations */ + if (g_str_has_prefix (host, "www.")) + host += 4; + else if (g_str_has_prefix (host, "m.")) + host += 2; + } - return success; + /* Whether "http(s)" scheme is used */ + is_https = (g_str_has_prefix (scheme, "http") + && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))); + + if (!host && is_https) + return FALSE; + + n_proxies = clapper_enhancer_proxy_list_get_n_proxies (_proxies); + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (_proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type) + && clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme) + && (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) { + if (name) + *name = clapper_enhancer_proxy_get_friendly_name (proxy); + + return TRUE; + } + } + + return FALSE; +} + +/** + * clapper_get_global_enhancer_proxies: + * + * Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects. + * + * This returns a global list of enhancer proxy objects. You can use it to inspect + * available enhancers without creating a new player instance. + * + * Remember to initialize Clapper library before using this function. + * + * Only enhancer properties with [flags@Clapper.EnhancerParamFlags.GLOBAL] flag can be + * set on proxies in this list. These are meant to be set ONLY by users, not applications + * as they carry over to all player instances (possibly including other apps). Applications + * should instead be changing properties with [flags@Clapper.EnhancerParamFlags.LOCAL] flag + * set from individual proxy lists from [property@Clapper.Player:enhancer-proxies] which + * will affect only that single player instance given list belongs to. + * + * Returns: (transfer none): a global #ClapperEnhancerProxyList of enhancer proxies. + * + * Since: 0.10 + */ +ClapperEnhancerProxyList * +clapper_get_global_enhancer_proxies (void) +{ + return _proxies; } diff --git a/src/lib/clapper/clapper.h b/src/lib/clapper/clapper.h index dd5491e5..8c94e56f 100644 --- a/src/lib/clapper/clapper.h +++ b/src/lib/clapper/clapper.h @@ -30,6 +30,8 @@ #include #include +#include +#include #include #include #include @@ -67,9 +69,12 @@ void clapper_init (int *argc, char **argv[]); CLAPPER_API gboolean clapper_init_check (int *argc, char **argv[]); -CLAPPER_API +CLAPPER_DEPRECATED gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name); +CLAPPER_API +ClapperEnhancerProxyList * clapper_get_global_enhancer_proxies (void); + G_END_DECLS #undef __CLAPPER_INSIDE__ diff --git a/src/lib/clapper/gst/clapper-enhancer-director-private.h b/src/lib/clapper/gst/clapper-enhancer-director-private.h index ebb025cf..39ae34b0 100644 --- a/src/lib/clapper/gst/clapper-enhancer-director-private.h +++ b/src/lib/clapper/gst/clapper-enhancer-director-private.h @@ -38,6 +38,6 @@ G_GNUC_INTERNAL ClapperEnhancerDirector * clapper_enhancer_director_new (void); G_GNUC_INTERNAL -ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GUri *uri, GCancellable *cancellable, GError **error); +ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error); G_END_DECLS diff --git a/src/lib/clapper/gst/clapper-enhancer-director.c b/src/lib/clapper/gst/clapper-enhancer-director.c index cdb1a162..2664faf9 100644 --- a/src/lib/clapper/gst/clapper-enhancer-director.c +++ b/src/lib/clapper/gst/clapper-enhancer-director.c @@ -20,11 +20,17 @@ #include #include "clapper-enhancer-director-private.h" -#include "../clapper-enhancers-loader-private.h" +#include "../clapper-enhancer-proxy-private.h" #include "../clapper-extractable-private.h" #include "../clapper-harvest-private.h" #include "../../shared/clapper-shared-utils-private.h" +#include "../clapper-functionalities-availability.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include "../clapper-enhancers-loader-private.h" +#endif + #define GST_CAT_DEFAULT clapper_enhancer_director_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -38,6 +44,8 @@ G_DEFINE_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER_TYPE_ typedef struct { + ClapperEnhancerDirector *director; + GList *filtered_proxies; GUri *uri; GCancellable *cancellable; GError **error; @@ -46,37 +54,61 @@ typedef struct static gpointer clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data) { - ClapperExtractable *extractable = NULL; - ClapperHarvest *harvest = clapper_harvest_new (); + ClapperEnhancerDirector *self = data->director; + GList *el; + ClapperHarvest *harvest = NULL; gboolean success = FALSE, cached = FALSE; + GST_DEBUG_OBJECT (self, "Extraction start"); + /* Cancelled during thread switching */ if (g_cancellable_is_cancelled (data->cancellable)) - goto finish; + return NULL; /* TODO: Cache lookup */ if (cached) { - // success = fill harvest from cache - goto finish; + // if ((success = fill harvest from cache)) + // return harvest; } - extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri ( - CLAPPER_TYPE_EXTRACTABLE, data->uri)); + GST_DEBUG_OBJECT (self, "Enhancer proxies for URI: %u", + g_list_length (data->filtered_proxies)); - /* Check just before extract */ - if (g_cancellable_is_cancelled (data->cancellable)) - goto finish; + for (el = data->filtered_proxies; el; el = g_list_next (el)) { + ClapperEnhancerProxy *proxy = CLAPPER_ENHANCER_PROXY_CAST (el->data); + ClapperExtractable *extractable = NULL; - success = clapper_extractable_extract (extractable, data->uri, - harvest, data->cancellable, data->error); + /* Check just before extract */ + if (g_cancellable_is_cancelled (data->cancellable)) + break; + +#if CLAPPER_WITH_ENHANCERS_LOADER + extractable = CLAPPER_EXTRACTABLE_CAST ( + clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_EXTRACTABLE)); +#endif + + if (G_LIKELY (extractable != NULL)) { + clapper_enhancer_proxy_apply_current_config_to_enhancer (proxy, (GObject *) extractable); + + harvest = clapper_harvest_new (); // fresh harvest for each extractable + + success = clapper_extractable_extract (extractable, data->uri, + harvest, data->cancellable, data->error); + gst_object_unref (extractable); + + /* We are done with extractable, but keep its harvest */ + if (success) + break; + + /* Clear harvest and try again with next enhancer */ + g_clear_object (&harvest); + } + } /* Cancelled during extract */ - if (g_cancellable_is_cancelled (data->cancellable)) { + if (g_cancellable_is_cancelled (data->cancellable)) success = FALSE; - goto finish; - } -finish: if (success) { if (!cached) { /* TODO: Store in cache */ @@ -94,7 +126,7 @@ finish: } } - gst_clear_object (&extractable); + GST_DEBUG_OBJECT (self, "Extraction finish"); return harvest; } @@ -116,11 +148,14 @@ clapper_enhancer_director_new (void) } ClapperHarvest * -clapper_enhancer_director_extract (ClapperEnhancerDirector *self, GUri *uri, +clapper_enhancer_director_extract (ClapperEnhancerDirector *self, + GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error) { ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1); + data->director = self; + data->filtered_proxies = filtered_proxies; data->uri = uri; data->cancellable = cancellable; data->error = error; diff --git a/src/lib/clapper/gst/clapper-enhancer-src.c b/src/lib/clapper/gst/clapper-enhancer-src.c index 198c0157..e0d8fa17 100644 --- a/src/lib/clapper/gst/clapper-enhancer-src.c +++ b/src/lib/clapper/gst/clapper-enhancer-src.c @@ -22,13 +22,17 @@ #include "clapper-enhancer-src-private.h" #include "clapper-enhancer-director-private.h" +#include "../clapper.h" +#include "../clapper-enhancer-proxy-list-private.h" #include "../clapper-extractable-private.h" #include "../clapper-harvest-private.h" -#include "../clapper-enhancers-loader-private.h" #define GST_CAT_DEFAULT clapper_enhancer_src_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); +#define CHECK_SCHEME_IS_HTTPS(scheme) (g_str_has_prefix (scheme, "http") \ + && (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0'))) + struct _ClapperEnhancerSrc { GstPushSrc parent; @@ -40,12 +44,15 @@ struct _ClapperEnhancerSrc gchar *uri; GUri *guri; + + ClapperEnhancerProxyList *enhancer_proxies; }; enum { PROP_0, PROP_URI, + PROP_ENHANCER_PROXIES, PROP_LAST }; @@ -62,10 +69,172 @@ clapper_enhancer_src_uri_handler_get_type (GType type) return GST_URI_SRC; } -static gpointer -_get_schemes_once (gpointer user_data G_GNUC_UNUSED) +/* + * _make_schemes: + * + * Make supported schemes array for a given interface type. + * The returned array consists of unique strings (no duplicates). + * + * Returns: (transfer full): all supported schemes by enhancers of @iface_type. + */ +static gchar ** +_make_schemes (gpointer user_data G_GNUC_UNUSED) { - return clapper_enhancers_loader_get_schemes (CLAPPER_TYPE_EXTRACTABLE); + ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies (); + GSList *found_schemes = NULL, *fs; + gchar **schemes_strv; + guint i, n_schemes, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + + GST_DEBUG ("Checking for supported URI schemes"); + + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + const gchar *schemes; + + if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE) + && (schemes = clapper_enhancer_proxy_get_extra_data (proxy, "X-Schemes"))) { + gchar **tmp_strv; + gint j; + + tmp_strv = g_strsplit (schemes, ";", 0); + + for (j = 0; tmp_strv[j]; ++j) { + const gchar *scheme = tmp_strv[j]; + + if (!found_schemes || !g_slist_find_custom (found_schemes, + scheme, (GCompareFunc) strcmp)) { + found_schemes = g_slist_append (found_schemes, g_strdup (scheme)); + GST_INFO ("Found supported URI scheme: \"%s\"", scheme); + } + } + + g_strfreev (tmp_strv); + } + } + + n_schemes = g_slist_length (found_schemes); + schemes_strv = g_new0 (gchar *, n_schemes + 1); + + fs = found_schemes; + for (i = 0; i < n_schemes; ++i) { + schemes_strv[i] = fs->data; + fs = fs->next; + } + + GST_DEBUG ("Total found URI schemes: %u", n_schemes); + + /* Since string pointers were taken, + * free list without content */ + g_slist_free (found_schemes); + + return schemes_strv; +} + +static inline const gchar * +_host_fixup (const gchar *host) +{ + /* Strip common subdomains, so plugins do not + * have to list all combinations */ + if (g_str_has_prefix (host, "www.")) + host += 4; + else if (g_str_has_prefix (host, "m.")) + host += 2; + + return host; +} + +/* + * _enhancer_check_for_uri: + * @self: a #ClapperEnhancerSrc + * @uri: a #GUri + * + * Check whether there is at least one enhancer for @uri in global list. + * This is used to reject URI early, thus making playbin choose different + * source element. It uses global list, since at this stage element is not + * yet placed within pipeline, so it cannot get proxies from player. + * + * Returns: whether at least one enhancer advertises support for given URI. + */ +static gboolean +_enhancer_check_for_uri (ClapperEnhancerSrc *self, GUri *uri) +{ + ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies (); + gboolean is_https; + guint i, n_proxies; + const gchar *scheme = g_uri_get_scheme (uri); + const gchar *host = g_uri_get_host (uri); + + if (host) + host = _host_fixup (host); + + GST_INFO_OBJECT (self, "Enhancer check, scheme: \"%s\", host: \"%s\"", + scheme, GST_STR_NULL (host)); + + /* Whether "http(s)" scheme is used */ + is_https = CHECK_SCHEME_IS_HTTPS (scheme); + + if (!host && is_https) + return FALSE; + + n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE) + && clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme) + && (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) + return TRUE; + } + + return FALSE; +} + +/* + * _filter_enhancers_for_uri: + * @self: a #ClapperEnhancerSrc + * @proxies: a #ClapperEnhancerProxyList + * @uri: a #GUri + * + * Finds all enhancer proxies of target implementing "Extractable" + * interface, which advertise support for given @uri. + * + * Returns: (transfer full): A sublist in the form of #GList with proxies. + */ +static GList * +_filter_enhancers_for_uri (ClapperEnhancerSrc *self, + ClapperEnhancerProxyList *proxies, GUri *uri) +{ + GList *sublist = NULL; + guint i, n_proxies; + gboolean is_https; + const gchar *scheme = g_uri_get_scheme (uri); + const gchar *host = g_uri_get_host (uri); + + if (host) + host = _host_fixup (host); + + GST_INFO_OBJECT (self, "Enhancer filter, scheme: \"%s\", host: \"%s\"", + scheme, GST_STR_NULL (host)); + + /* Whether "http(s)" scheme is used */ + is_https = CHECK_SCHEME_IS_HTTPS (scheme); + + if (!host && is_https) + return NULL; + + n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE) + && clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme) + && (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) { + sublist = g_list_append (sublist, gst_object_ref (proxy)); + break; + } + } + + return sublist; } static const gchar *const * @@ -73,7 +242,7 @@ clapper_enhancer_src_uri_handler_get_protocols (GType type) { static GOnce schemes_once = G_ONCE_INIT; - g_once (&schemes_once, _get_schemes_once, NULL); + g_once (&schemes_once, (GThreadFunc) _make_schemes, NULL); return (const gchar *const *) schemes_once.retval; } @@ -130,8 +299,7 @@ clapper_enhancer_src_uri_handler_set_uri (GstURIHandler *handler, return FALSE; } - if (!clapper_enhancers_loader_check (CLAPPER_TYPE_EXTRACTABLE, - g_uri_get_scheme (guri), g_uri_get_host (guri), NULL)) { + if (!_enhancer_check_for_uri (self, guri)) { g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "None of the available enhancers can handle this URI"); g_uri_unref (guri); @@ -296,6 +464,8 @@ static GstFlowReturn clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf) { ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (push_src); + ClapperEnhancerProxyList *proxies; + GList *filtered_proxies; GUri *guri; GCancellable *cancellable; ClapperHarvest *harvest; @@ -316,12 +486,28 @@ clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf) self->director = clapper_enhancer_director_new (); GST_OBJECT_LOCK (self); + + if (G_LIKELY (self->enhancer_proxies != NULL)) { + GST_INFO_OBJECT (self, "Using enhancer proxies: %" GST_PTR_FORMAT, self->enhancer_proxies); + proxies = gst_object_ref (self->enhancer_proxies); + } else { + /* Compat for old ClapperDiscoverer feature that does not set this property */ + GST_WARNING_OBJECT (self, "Falling back to using global enhancer proxy list!"); + proxies = gst_object_ref (clapper_get_global_enhancer_proxies ()); + } + guri = g_uri_ref (self->guri); cancellable = g_object_ref (self->cancellable); + GST_OBJECT_UNLOCK (self); - harvest = clapper_enhancer_director_extract (self->director, guri, cancellable, &error); + filtered_proxies = _filter_enhancers_for_uri (self, proxies, guri); + gst_object_unref (proxies); + harvest = clapper_enhancer_director_extract (self->director, + filtered_proxies, guri, cancellable, &error); + + g_clear_list (&filtered_proxies, gst_object_unref); g_uri_unref (guri); g_object_unref (cancellable); @@ -385,6 +571,16 @@ clapper_enhancer_src_query (GstBaseSrc *base_src, GstQuery *query) return ret; } +static void +clapper_enhancer_src_set_enhancer_proxies (ClapperEnhancerSrc *self, + ClapperEnhancerProxyList *enhancer_proxies) +{ + GST_OBJECT_LOCK (self); + gst_object_replace ((GstObject **) &self->enhancer_proxies, + GST_OBJECT_CAST (enhancer_proxies)); + GST_OBJECT_UNLOCK (self); +} + static void clapper_enhancer_src_init (ClapperEnhancerSrc *self) { @@ -413,6 +609,7 @@ clapper_enhancer_src_finalize (GObject *object) g_clear_object (&self->cancellable); g_free (self->uri); g_clear_pointer (&self->guri, g_uri_unref); + gst_clear_object (&self->enhancer_proxies); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -433,6 +630,9 @@ clapper_enhancer_src_set_property (GObject *object, guint prop_id, } break; } + case PROP_ENHANCER_PROXIES: + clapper_enhancer_src_set_enhancer_proxies (self, g_value_get_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -485,6 +685,10 @@ clapper_enhancer_src_class_init (ClapperEnhancerSrcClass *klass) "URI", "URI", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies", + NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gst_element_class_add_static_pad_template (gstelement_class, &src_template); diff --git a/src/lib/clapper/gst/clapper-plugin.c b/src/lib/clapper/gst/clapper-plugin.c index 4c26efb2..f6d51165 100644 --- a/src/lib/clapper/gst/clapper-plugin.c +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -21,31 +21,50 @@ #include -#include "clapper-plugin-private.h" -#include "../clapper-functionalities-availability.h" - -#if CLAPPER_WITH_ENHANCERS_LOADER +#include "../clapper.h" #include "clapper-enhancer-src-private.h" -#include "../clapper-extractable-private.h" -#include "../clapper-enhancers-loader-private.h" -#endif +#include "clapper-plugin-private.h" #include "clapper-uri-list-demux-private.h" +/* + * clapper_gst_plugin_has_enhancers: + * @iface_type: an interface #GType + * + * Check if any enhancer implementing given interface type is available. + * + * Returns: whether any enhancer was found. + */ +static gboolean +clapper_gst_plugin_has_enhancers (ClapperEnhancerProxyList *proxies, GType iface_type) +{ + guint i, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies); + + for (i = 0; i < n_proxies; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type)) + return TRUE; + } + + return FALSE; +} + gboolean clapper_gst_plugin_init (GstPlugin *plugin) { gboolean res = FALSE; + ClapperEnhancerProxyList *global_proxies; -#if CLAPPER_WITH_ENHANCERS_LOADER gst_plugin_add_dependency_simple (plugin, "CLAPPER_ENHANCERS_PATH", CLAPPER_ENHANCERS_PATH, NULL, GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY); + global_proxies = clapper_get_global_enhancer_proxies (); + /* Avoid registering an URI handler without schemes */ - if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE)) + if (clapper_gst_plugin_has_enhancers (global_proxies, CLAPPER_TYPE_EXTRACTABLE)) res |= GST_ELEMENT_REGISTER (clapperenhancersrc, plugin); -#endif res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin); diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index 4ca10ea5..f86d38ec 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -54,6 +54,7 @@ config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) config_h.set_quoted('PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') config_h.set_quoted('PLUGIN_DESC', 'Clapper elements') config_h.set_quoted('PLUGIN_LICENSE', 'LGPL') +config_h.set_quoted('CLAPPER_ENHANCERS_ID', 'com.github.rafostar.Clapper.Enhancers') config_h.set_quoted('CLAPPER_ENHANCERS_PATH', clapper_enhancers_dir) configure_file( @@ -109,6 +110,8 @@ clapper_headers = [ 'clapper.h', 'clapper-enums.h', 'clapper-audio-stream.h', + 'clapper-enhancer-proxy.h', + 'clapper-enhancer-proxy-list.h', 'clapper-extractable.h', 'clapper-feature.h', 'clapper-harvest.h', @@ -130,6 +133,8 @@ clapper_sources = [ 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', + 'clapper-enhancer-proxy.c', + 'clapper-enhancer-proxy-list.c', 'clapper-extractable.c', 'clapper-feature.c', 'clapper-features-bus.c', @@ -148,6 +153,8 @@ clapper_sources = [ 'clapper-utils.c', 'clapper-video-stream.c', 'gst/clapper-plugin.c', + 'gst/clapper-enhancer-src.c', + 'gst/clapper-enhancer-director.c', 'gst/clapper-uri-list-demux.c', '../shared/clapper-shared-utils.c', ] @@ -170,8 +177,6 @@ if clapper_with_enhancers_loader clapper_deps += peas_dep clapper_sources += [ 'clapper-enhancers-loader.c', - 'gst/clapper-enhancer-src.c', - 'gst/clapper-enhancer-director.c', ] clapper_available_functionalities += 'enhancers-loader' endif