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