From 976bcc338f67d096f2bcb4065e28fc0c870ea168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 22 May 2025 18:25:38 +0200 Subject: [PATCH 1/4] clapper: Add "Reactable" interface An interface for creating enhancers that react to the playback and/or events that should influence it. --- src/lib/clapper/clapper-feature.c | 2 + src/lib/clapper/clapper-feature.h | 2 +- src/lib/clapper/clapper-reactable.c | 210 +++++++++++++++++++++++++ src/lib/clapper/clapper-reactable.h | 228 ++++++++++++++++++++++++++++ src/lib/clapper/clapper.h | 1 + src/lib/clapper/meson.build | 2 + 6 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/lib/clapper/clapper-reactable.c create mode 100644 src/lib/clapper/clapper-reactable.h diff --git a/src/lib/clapper/clapper-feature.c b/src/lib/clapper/clapper-feature.c index fa0a0d7e..edfb1066 100644 --- a/src/lib/clapper/clapper-feature.c +++ b/src/lib/clapper/clapper-feature.c @@ -30,6 +30,8 @@ * virtual functions logic, while for controlling playback implementation * may call [method@Gst.Object.get_parent] to acquire a weak reference on * a parent [class@Clapper.Player] object feature was added to. + * + * Deprecated: 0.10: Use [iface@Clapper.Reactable] instead. */ #include "clapper-feature.h" diff --git a/src/lib/clapper/clapper-feature.h b/src/lib/clapper/clapper-feature.h index d6bb8f2a..118b6cdf 100644 --- a/src/lib/clapper/clapper-feature.h +++ b/src/lib/clapper/clapper-feature.h @@ -37,7 +37,7 @@ G_BEGIN_DECLS #define CLAPPER_TYPE_FEATURE (clapper_feature_get_type()) #define CLAPPER_FEATURE_CAST(obj) ((ClapperFeature *)(obj)) -CLAPPER_API +CLAPPER_DEPRECATED_FOR(ClapperReactable) G_DECLARE_DERIVABLE_TYPE (ClapperFeature, clapper_feature, CLAPPER, FEATURE, GstObject) /** diff --git a/src/lib/clapper/clapper-reactable.c b/src/lib/clapper/clapper-reactable.c new file mode 100644 index 00000000..c883c590 --- /dev/null +++ b/src/lib/clapper/clapper-reactable.c @@ -0,0 +1,210 @@ +/* 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. + */ + +/** + * ClapperReactable: + * + * An interface for creating enhancers that react to the + * playback and/or events that should influence it. + * + * Since: 0.10 + */ + +#include "clapper-reactable.h" +#include "clapper-utils-private.h" + +#define CLAPPER_REACTABLE_DO_WITH_QUEUE(reactable, _queue_dst, ...) { \ + ClapperPlayer *_player = clapper_reactable_get_player (reactable); \ + if (G_LIKELY (_player != NULL)) { \ + *_queue_dst = clapper_player_get_queue (_player); \ + __VA_ARGS__ \ + gst_object_unref (_player); }} + +G_DEFINE_INTERFACE (ClapperReactable, clapper_reactable, GST_TYPE_OBJECT); + +static void +clapper_reactable_default_init (ClapperReactableInterface *iface) +{ +} + +/** + * clapper_reactable_get_player: + * @reactable: a #ClapperReactable + * + * Get the [class@Clapper.Player] that this reactable is reacting to. + * + * This is meant to be used in implementations where reaction goes the + * other way around (from enhancer plugin to the player). For example + * some external event needs to influence parent player object like + * changing its state, seeking, etc. + * + * Note that enhancers are working in a non-main application thread, thus + * if you need to do operations on a [class@Clapper.Queue] such as adding/removing + * items, you need to switch thread first. Otherwise this will not be thread safe + * for applications that use single threaded toolkits such as #GTK. You can do this + * manually or use provided reactable convenience functions. + * + * Due to the threaded nature, you should also avoid comparisons to the current + * properties values in the player or its queue. While these are thread safe, there + * is no guarantee that values/objects between threads are still the same in both + * (or still exist). For example, instead of using [property@Clapper.Queue:current_item], + * monitor it with implemented [vfunc@Clapper.Reactable.played_item_changed] instead, + * as these functions are all serialized into your implementation thread. + * + * Returns: (transfer full) (nullable): A reference to the parent #ClapperPlayer. + * + * Since: 0.10 + */ +ClapperPlayer * +clapper_reactable_get_player (ClapperReactable *self) +{ + g_return_val_if_fail (CLAPPER_IS_REACTABLE (self), NULL); + + return CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (self))); +} + +/** + * clapper_reactable_queue_append_sync: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem + * + * A convenience function that within application main thread synchronously appends + * an @item to the playback queue of the player that @reactable belongs to. + * + * Reactable enhancers should only modify the queue from the application + * main thread, switching thread either themselves or using this convenience + * function that does so. + * + * Note that this function will do no operation if called when there is no player + * set yet (e.g. inside enhancer construction) or if enhancer outlived the parent + * instance somehow. Both cases are considered to be implementation bug. + * + * Since: 0.10 + */ +void +clapper_reactable_queue_append_sync (ClapperReactable *self, ClapperMediaItem *item) +{ + ClapperQueue *queue; + + g_return_if_fail (CLAPPER_IS_REACTABLE (self)); + g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); + + CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, { + clapper_utils_queue_append_on_main_sync (queue, item); + }); +} + +/** + * clapper_reactable_queue_insert_sync: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem + * @after_item: a #ClapperMediaItem after which to insert or %NULL to prepend + * + * A convenience function that within application main thread synchronously inserts + * an @item to the playback queue position after @after_item of the player that + * @reactable belongs to. + * + * This function uses @after_item instead of position index in order to ensure + * desired position does not change during thread switching. + * + * Reactable enhancers should only modify the queue from the application + * main thread, switching thread either themselves or using this convenience + * function that does so. + * + * Note that this function will do no operation if called when there is no player + * set yet (e.g. inside enhancer construction) or if enhancer outlived the parent + * instance somehow. Both cases are considered to be implementation bug. + * + * Since: 0.10 + */ +void +clapper_reactable_queue_insert_sync (ClapperReactable *self, + ClapperMediaItem *item, ClapperMediaItem *after_item) +{ + ClapperQueue *queue; + + g_return_if_fail (CLAPPER_IS_REACTABLE (self)); + g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); + g_return_if_fail (after_item == NULL || CLAPPER_IS_MEDIA_ITEM (after_item)); + + CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, { + clapper_utils_queue_insert_on_main_sync (queue, item, after_item); + }); +} + +/** + * clapper_reactable_queue_remove_sync: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem + * + * A convenience function that within application main thread synchronously removes + * an @item from the playback queue of the player that @reactable belongs to. + * + * Reactable enhancers should only modify the queue from the application + * main thread, switching thread either themselves or using this convenience + * function that does so. + * + * Note that this function will do no operation if called when there is no player + * set yet (e.g. inside enhancer construction) or if enhancer outlived the parent + * instance somehow. Both cases are considered to be implementation bug. + * + * Since: 0.10 + */ +void +clapper_reactable_queue_remove_sync (ClapperReactable *self, ClapperMediaItem *item) +{ + ClapperQueue *queue; + + g_return_if_fail (CLAPPER_IS_REACTABLE (self)); + g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); + + CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, { + clapper_utils_queue_remove_on_main_sync (queue, item); + }); +} + +/** + * clapper_reactable_queue_clear_sync: + * @reactable: a #ClapperReactable + * + * A convenience function that within application main thread synchronously clears + * the playback queue of the player that @reactable belongs to. + * + * Reactable enhancers should only modify the queue from the application + * main thread, switching thread either themselves or using this convenience + * function that does so. + * + * Note that this function will do no operation if called when there is no player + * set yet (e.g. inside enhancer construction) or if enhancer outlived the parent + * instance somehow. Both cases are considered to be implementation bug. + * + * Since: 0.10 + */ +void +clapper_reactable_queue_clear_sync (ClapperReactable *self) +{ + ClapperQueue *queue; + + g_return_if_fail (CLAPPER_IS_REACTABLE (self)); + g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); + + CLAPPER_REACTABLE_DO_WITH_QUEUE (self, &queue, { + clapper_utils_queue_clear_on_main_sync (queue); + }); +} diff --git a/src/lib/clapper/clapper-reactable.h b/src/lib/clapper/clapper-reactable.h new file mode 100644 index 00000000..e53f2f8a --- /dev/null +++ b/src/lib/clapper/clapper-reactable.h @@ -0,0 +1,228 @@ +/* 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 +#include + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_REACTABLE (clapper_reactable_get_type()) +#define CLAPPER_REACTABLE_CAST(obj) ((ClapperReactable *)(obj)) + +CLAPPER_API +G_DECLARE_INTERFACE (ClapperReactable, clapper_reactable, CLAPPER, REACTABLE, GstObject) + +/** + * ClapperReactableInterface: + * @parent_iface: The parent interface structure. + * @state_changed: Player state changed. + * @position_changed: Player position changed. + * @speed_changed: Player speed changed. + * @volume_changed: Player volume changed. + * @mute_changed: Player mute state changed. + * @played_item_changed: New media item started playing. + * @item_updated: An item in queue got updated. + * @queue_item_added: An item was added to the queue. + * @queue_item_removed: An item was removed from queue. + * @queue_item_repositioned: An item changed position within queue. + * @queue_cleared: All items were removed from queue. + * @queue_progression_changed: Progression mode of the queue was changed. + */ +struct _ClapperReactableInterface +{ + GTypeInterface parent_iface; + + /** + * ClapperReactableInterface::state_changed: + * @reactable: a #ClapperReactable + * @state: a #ClapperPlayerState + * + * Player state changed. + * + * Since: 0.10 + */ + void (* state_changed) (ClapperReactable *reactable, ClapperPlayerState state); + + /** + * ClapperReactableInterface::position_changed: + * @reactable: a #ClapperReactable + * @position: a decimal number with current position in seconds + * + * Player position changed. + * + * Since: 0.10 + */ + void (* position_changed) (ClapperReactable *reactable, gdouble position); + + /** + * ClapperReactableInterface::speed_changed: + * @reactable: a #ClapperReactable + * @speed: the playback speed multiplier + * + * Player speed changed. + * + * Since: 0.10 + */ + void (* speed_changed) (ClapperReactable *reactable, gdouble speed); + + /** + * ClapperReactableInterface::volume_changed: + * @reactable: a #ClapperReactable + * @volume: the volume level + * + * Player volume changed. + * + * Since: 0.10 + */ + void (* volume_changed) (ClapperReactable *reactable, gdouble volume); + + /** + * ClapperReactableInterface::mute_changed: + * @reactable: a #ClapperReactable + * @mute: %TRUE if player is muted, %FALSE otherwise + * + * Player mute state changed. + * + * Since: 0.10 + */ + void (* mute_changed) (ClapperReactable *reactable, gboolean mute); + + /** + * ClapperReactableInterface::played_item_changed: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem that is now playing + * + * New media item started playing. All following events (such as position changes) + * will be related to this @item from now on. + * + * Since: 0.10 + */ + void (* played_item_changed) (ClapperReactable *reactable, ClapperMediaItem *item); + + /** + * ClapperReactableInterface::item_updated: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem that was updated + * + * An item in queue got updated. + * + * This might be (or not) currently played item. + * Implementations can compare it against the last item from + * [vfunc@Clapper.Reactable.played_item_changed] if they + * need to know that. + * + * Since: 0.10 + */ + void (* item_updated) (ClapperReactable *reactable, ClapperMediaItem *item); + + /** + * ClapperReactableInterface::queue_item_added: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem that was added + * @index: position at which @item was placed in queue + * + * An item was added to the queue. + * + * Since: 0.10 + */ + void (* queue_item_added) (ClapperReactable *reactable, ClapperMediaItem *item, guint index); + + /** + * ClapperReactableInterface::queue_item_removed: + * @reactable: a #ClapperReactable + * @item: a #ClapperMediaItem that was removed + * @index: position from which @item was removed in queue + * + * An item was removed from queue. + * + * Implementations that are interested in queue items removal + * should also implement [vfunc@Clapper.Reactable.queue_cleared]. + * + * Since: 0.10 + */ + void (* queue_item_removed) (ClapperReactable *reactable, ClapperMediaItem *item, guint index); + + /** + * ClapperReactableInterface::queue_item_repositioned: + * @reactable: a #ClapperReactable + * @before: position from which #ClapperMediaItem was removed + * @after: position at which #ClapperMediaItem was inserted after removal + * + * An item changed position within queue. + * + * Since: 0.10 + */ + void (* queue_item_repositioned) (ClapperReactable *reactable, guint before, guint after); + + /** + * ClapperReactableInterface::queue_cleared: + * @reactable: a #ClapperReactable + * + * All items were removed from queue. + * + * Note that in such event [vfunc@Clapper.Reactable.queue_item_removed] + * will NOT be called for each item for performance reasons. You probably + * want to implement this function if you also implemented item removal. + * + * Since: 0.10 + */ + void (* queue_cleared) (ClapperReactable *reactable); + + /** + * ClapperReactableInterface::queue_progression_changed: + * @reactable: a #ClapperReactable + * @mode: a #ClapperQueueProgressionMode + * + * Progression mode of the queue was changed. + * + * Since: 0.10 + */ + void (* queue_progression_changed) (ClapperReactable *reactable, ClapperQueueProgressionMode mode); + + /*< private >*/ + gpointer padding[8]; +}; + +CLAPPER_API +ClapperPlayer * clapper_reactable_get_player (ClapperReactable *reactable); + +CLAPPER_API +void clapper_reactable_queue_append_sync (ClapperReactable *reactable, ClapperMediaItem *item); + +CLAPPER_API +void clapper_reactable_queue_insert_sync (ClapperReactable *reactable, ClapperMediaItem *item, ClapperMediaItem *after_item); + +CLAPPER_API +void clapper_reactable_queue_remove_sync (ClapperReactable *reactable, ClapperMediaItem *item); + +CLAPPER_API +void clapper_reactable_queue_clear_sync (ClapperReactable *reactable); + +G_END_DECLS diff --git a/src/lib/clapper/clapper.h b/src/lib/clapper/clapper.h index aacad5bf..c540eff4 100644 --- a/src/lib/clapper/clapper.h +++ b/src/lib/clapper/clapper.h @@ -45,6 +45,7 @@ #include #include +#include #include #include diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index 77de1517..522f00cd 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -120,6 +120,7 @@ clapper_headers = [ 'clapper-media-item.h', 'clapper-player.h', 'clapper-queue.h', + 'clapper-reactable.h', 'clapper-stream.h', 'clapper-stream-list.h', 'clapper-subtitle-stream.h', @@ -147,6 +148,7 @@ clapper_sources = [ 'clapper-playbin-bus.c', 'clapper-player.c', 'clapper-queue.c', + 'clapper-reactable.c', 'clapper-stream.c', 'clapper-stream-list.c', 'clapper-subtitle-stream.c', From a6ca0b726c5394fb11ee95a4eab0e343b2d15e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Thu, 5 Jun 2025 20:16:43 +0200 Subject: [PATCH 2/4] clapper: Implement reactables manager An object for managing instances of reactable type of enhancers. Based on/similar to features manager which along with Clapper features objects gets deprecated in favour of reactables. --- src/lib/clapper/clapper-basic-functions.c | 2 + src/lib/clapper/clapper-cache.c | 6 + .../clapper-enhancer-proxy-list-private.h | 4 + src/lib/clapper/clapper-enhancer-proxy-list.c | 23 + .../clapper/clapper-enhancer-proxy-private.h | 3 + src/lib/clapper/clapper-enhancer-proxy.c | 91 ++- src/lib/clapper/clapper-enhancers-loader.c | 35 +- src/lib/clapper/clapper-media-item-private.h | 2 +- src/lib/clapper/clapper-media-item.c | 7 + src/lib/clapper/clapper-playbin-bus.c | 4 + src/lib/clapper/clapper-player-private.h | 3 + src/lib/clapper/clapper-player.c | 23 + src/lib/clapper/clapper-queue.c | 34 +- .../clapper-reactables-manager-private.h | 82 +++ src/lib/clapper/clapper-reactables-manager.c | 533 ++++++++++++++++++ src/lib/clapper/clapper-timeline.c | 16 +- src/lib/clapper/clapper-utils-private.h | 3 + src/lib/clapper/clapper-utils.c | 56 ++ src/lib/clapper/gst/clapper-plugin.c | 27 +- src/lib/clapper/meson.build | 1 + 20 files changed, 868 insertions(+), 87 deletions(-) create mode 100644 src/lib/clapper/clapper-reactables-manager-private.h create mode 100644 src/lib/clapper/clapper-reactables-manager.c diff --git a/src/lib/clapper/clapper-basic-functions.c b/src/lib/clapper/clapper-basic-functions.c index 32e58f45..f128b40e 100644 --- a/src/lib/clapper/clapper-basic-functions.c +++ b/src/lib/clapper/clapper-basic-functions.c @@ -29,6 +29,7 @@ #include "clapper-app-bus-private.h" #include "clapper-features-bus-private.h" #include "clapper-enhancer-proxy-list-private.h" +#include "clapper-reactables-manager-private.h" #include "gst/clapper-plugin-private.h" #include "clapper-functionalities-availability.h" @@ -56,6 +57,7 @@ clapper_init_check_internal (int *argc, char **argv[]) clapper_playbin_bus_initialize (); clapper_app_bus_initialize (); clapper_features_bus_initialize (); + clapper_reactables_manager_initialize (); _proxies = clapper_enhancer_proxy_list_new_named ("global-proxy-list"); diff --git a/src/lib/clapper/clapper-cache.c b/src/lib/clapper/clapper-cache.c index 30774800..c15c9855 100644 --- a/src/lib/clapper/clapper-cache.c +++ b/src/lib/clapper/clapper-cache.c @@ -20,12 +20,14 @@ #include "clapper-cache-private.h" #include "clapper-version.h" #include "clapper-extractable.h" +#include "clapper-reactable.h" #define CLAPPER_CACHE_HEADER "CLAPPER" typedef enum { CLAPPER_CACHE_IFACE_EXTRACTABLE = 1, + CLAPPER_CACHE_IFACE_REACTABLE, } ClapperCacheIfaces; static GArray *enum_registry = NULL; @@ -243,6 +245,8 @@ clapper_cache_read_iface (const gchar **data) switch (iface_id) { case CLAPPER_CACHE_IFACE_EXTRACTABLE: return CLAPPER_TYPE_EXTRACTABLE; + case CLAPPER_CACHE_IFACE_REACTABLE: + return CLAPPER_TYPE_REACTABLE; default: return 0; } @@ -433,6 +437,8 @@ clapper_cache_store_iface (GByteArray *bytes, GType iface) if (iface == CLAPPER_TYPE_EXTRACTABLE) iface_id = CLAPPER_CACHE_IFACE_EXTRACTABLE; + else if (iface == CLAPPER_TYPE_REACTABLE) + iface_id = CLAPPER_CACHE_IFACE_REACTABLE; else return FALSE; diff --git a/src/lib/clapper/clapper-enhancer-proxy-list-private.h b/src/lib/clapper/clapper-enhancer-proxy-list-private.h index c25b4024..a48162c2 100644 --- a/src/lib/clapper/clapper-enhancer-proxy-list-private.h +++ b/src/lib/clapper/clapper-enhancer-proxy-list-private.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include "clapper-enhancer-proxy-list.h" #include "clapper-enhancer-proxy.h" @@ -38,4 +39,7 @@ void clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyL G_GNUC_INTERNAL void clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *list); +G_GNUC_INTERNAL +gboolean clapper_enhancer_proxy_list_has_proxy_with_interface (ClapperEnhancerProxyList *list, GType iface_type); + G_END_DECLS diff --git a/src/lib/clapper/clapper-enhancer-proxy-list.c b/src/lib/clapper/clapper-enhancer-proxy-list.c index 619436bd..228117b8 100644 --- a/src/lib/clapper/clapper-enhancer-proxy-list.c +++ b/src/lib/clapper/clapper-enhancer-proxy-list.c @@ -161,6 +161,29 @@ clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *self) g_ptr_array_sort_values (self->proxies, (GCompareFunc) _sort_values_by_name); } +/* + * clapper_enhancer_proxy_list_has_proxy_with_interface: + * @iface_type: an interface #GType + * + * Check if any enhancer implementing given interface type is available. + * + * Returns: whether any enhancer proxy was found. + */ +gboolean +clapper_enhancer_proxy_list_has_proxy_with_interface (ClapperEnhancerProxyList *self, GType iface_type) +{ + guint i; + + for (i = 0; i < self->proxies->len; ++i) { + ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (self, i); + + if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type)) + return TRUE; + } + + return FALSE; +} + /** * clapper_enhancer_proxy_list_get_proxy: * @list: a #ClapperEnhancerProxyList diff --git a/src/lib/clapper/clapper-enhancer-proxy-private.h b/src/lib/clapper/clapper-enhancer-proxy-private.h index fb1ff659..f7d20a42 100644 --- a/src/lib/clapper/clapper-enhancer-proxy-private.h +++ b/src/lib/clapper/clapper-enhancer-proxy-private.h @@ -45,6 +45,9 @@ void clapper_enhancer_proxy_export_to_cache (ClapperEnhancerProxy *proxy); G_GNUC_INTERNAL GObject * clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *proxy); +G_GNUC_INTERNAL +gboolean clapper_enhancer_proxy_has_locally_set (ClapperEnhancerProxy *proxy, const gchar *property_name); + G_GNUC_INTERNAL GstStructure * clapper_enhancer_proxy_make_current_config (ClapperEnhancerProxy *proxy); diff --git a/src/lib/clapper/clapper-enhancer-proxy.c b/src/lib/clapper/clapper-enhancer-proxy.c index 2b78ed5e..367667d9 100644 --- a/src/lib/clapper/clapper-enhancer-proxy.c +++ b/src/lib/clapper/clapper-enhancer-proxy.c @@ -48,6 +48,9 @@ #include "clapper-basic-functions.h" #include "clapper-cache-private.h" #include "clapper-extractable.h" +#include "clapper-reactable.h" +#include "clapper-player-private.h" +#include "clapper-utils-private.h" #include "clapper-enums.h" #include "clapper-functionalities-availability.h" @@ -454,7 +457,7 @@ clapper_enhancer_proxy_export_to_cache (ClapperEnhancerProxy *self) gboolean clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *self, GObject *enhancer) { - GType enhancer_types[1] = { CLAPPER_TYPE_EXTRACTABLE }; + const GType enhancer_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_REACTABLE }; GType *ifaces; GParamSpec **pspecs; GParamFlags enhancer_flags; @@ -500,6 +503,18 @@ clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *self) return self->peas_info; } +gboolean +clapper_enhancer_proxy_has_locally_set (ClapperEnhancerProxy *self, const gchar *property_name) +{ + gboolean has_field; + + GST_OBJECT_LOCK (self); + has_field = (self->local_config && gst_structure_has_field (self->local_config, property_name)); + GST_OBJECT_UNLOCK (self); + + return has_field; +} + static gboolean _apply_config_cb (GQuark field_id, const GValue *value, GObject *enhancer) { @@ -527,6 +542,7 @@ clapper_enhancer_proxy_make_current_config (ClapperEnhancerProxy *self) /* Using "has_field", as set value might be %NULL */ if ((pspec->flags & CLAPPER_ENHANCER_PARAM_LOCAL) + && self->local_config && gst_structure_has_field (self->local_config, pspec->name)) { if (!merged_config) merged_config = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); @@ -540,44 +556,13 @@ clapper_enhancer_proxy_make_current_config (ClapperEnhancerProxy *self) 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); + GValue value = G_VALUE_INIT; - 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; - } + if (G_LIKELY (clapper_utils_set_value_from_variant (&value, val))) { + if (!merged_config) + merged_config = gst_structure_new_empty (CONFIG_STRUCTURE_NAME); + + gst_structure_take_value (merged_config, pspec->name, &value); } } @@ -947,6 +932,20 @@ _structure_take_value_by_pspec (ClapperEnhancerProxy *self, return FALSE; } +static void +_trigger_reactable_configure_take (ClapperEnhancerProxy *self, GstStructure *structure) +{ + ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); + + if (G_LIKELY (player != NULL)) { + clapper_reactables_manager_trigger_configure_take_config ( + player->reactables_manager, self, structure); + gst_object_unref (player); + } else { + gst_structure_free (structure); + } +} + /** * clapper_enhancer_proxy_set_locally: * @proxy: a #ClapperEnhancerProxy @@ -1017,10 +1016,10 @@ clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *self, const gchar *fir _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); + if (clapper_enhancer_proxy_target_has_interface (self, CLAPPER_TYPE_REACTABLE)) + _trigger_reactable_configure_take (self, structure); + else + gst_structure_free (structure); } /** @@ -1084,10 +1083,10 @@ clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *self, GHash _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); + if (clapper_enhancer_proxy_target_has_interface (self, CLAPPER_TYPE_REACTABLE)) + _trigger_reactable_configure_take (self, structure); + else + gst_structure_free (structure); } static void diff --git a/src/lib/clapper/clapper-enhancers-loader.c b/src/lib/clapper/clapper-enhancers-loader.c index 1969376b..aeb95dd7 100644 --- a/src/lib/clapper/clapper-enhancers-loader.c +++ b/src/lib/clapper/clapper-enhancers-loader.c @@ -33,6 +33,9 @@ static HMODULE _enhancers_dll_handle = NULL; // Supported interfaces #include "clapper-extractable.h" +#include "clapper-reactable.h" + +#include #define GST_CAT_DEFAULT clapper_enhancers_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -110,6 +113,36 @@ clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) ClapperEnhancerProxy *proxy; gboolean filled; + /* FIXME: 1.0: Remove together with features code and manager. + * These would clash with each other, so avoid loading these + * as enhancers when also compiled as part of the library. */ +#if (CLAPPER_HAVE_MPRIS || CLAPPER_HAVE_DISCOVERER || CLAPPER_HAVE_SERVER) + guint f_index; + const gchar *module_name = peas_plugin_info_get_module_name (info); + const gchar *ported_features[] = { +#if CLAPPER_HAVE_MPRIS + "clapper-mpris", +#endif +#if CLAPPER_HAVE_DISCOVERER + "clapper-discoverer", +#endif +#if CLAPPER_HAVE_SERVER + "clapper-server", +#endif + }; + + for (f_index = 0; f_index < G_N_ELEMENTS (ported_features); ++f_index) { + if (strcmp (module_name, ported_features[f_index]) == 0) { + GST_INFO ("Skipped \"%s\" enhancer module, since its" + " loaded from deprecated feature object", module_name); + g_clear_object (&info); + } + } + + if (!info) // cleared when exists as feature + continue; +#endif + /* 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); @@ -118,7 +151,7 @@ clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies) * 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 }; + const GType main_types[] = { CLAPPER_TYPE_EXTRACTABLE, CLAPPER_TYPE_REACTABLE }; guint j; /* We cannot ask libpeas for "any" of our main interfaces, so try each one until found */ diff --git a/src/lib/clapper/clapper-media-item-private.h b/src/lib/clapper/clapper-media-item-private.h index 2ddc5bea..ab218b6a 100644 --- a/src/lib/clapper/clapper-media-item-private.h +++ b/src/lib/clapper/clapper-media-item-private.h @@ -23,7 +23,7 @@ #include #include "clapper-media-item.h" -#include "clapper-player-private.h" +#include "clapper-player.h" #include "clapper-app-bus-private.h" G_BEGIN_DECLS diff --git a/src/lib/clapper/clapper-media-item.c b/src/lib/clapper/clapper-media-item.c index 2532c3cf..b4e925c7 100644 --- a/src/lib/clapper/clapper-media-item.c +++ b/src/lib/clapper/clapper-media-item.c @@ -30,6 +30,7 @@ #include "clapper-timeline-private.h" #include "clapper-player-private.h" #include "clapper-playbin-bus-private.h" +#include "clapper-reactables-manager-private.h" #include "clapper-features-manager-private.h" #include "clapper-utils-private.h" @@ -565,6 +566,8 @@ clapper_media_item_populate_tags (ClapperMediaItem *self, const GstTagList *tags if (changed && player) { ClapperFeaturesManager *features_manager; + if (player->reactables_manager) + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } @@ -602,6 +605,8 @@ clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagLis if (changed) { ClapperFeaturesManager *features_manager; + if (player->reactables_manager) + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } @@ -645,6 +650,8 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco if (changed) { ClapperFeaturesManager *features_manager; + if (player->reactables_manager) + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } diff --git a/src/lib/clapper/clapper-playbin-bus.c b/src/lib/clapper/clapper-playbin-bus.c index faa815cc..0260455b 100644 --- a/src/lib/clapper/clapper-playbin-bus.c +++ b/src/lib/clapper/clapper-playbin-bus.c @@ -173,6 +173,8 @@ _update_current_duration (ClapperPlayer *player) if (clapper_media_item_set_duration (player->played_item, duration_dbl, player->app_bus)) { ClapperFeaturesManager *features_manager; + if (player->reactables_manager) + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, player->played_item); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, player->played_item); } @@ -1046,6 +1048,8 @@ _handle_stream_start_msg (GstMessage *msg, ClapperPlayer *player) if (G_LIKELY (changed)) { clapper_queue_handle_played_item_changed (player->queue, player->played_item, player->app_bus); + if (player->reactables_manager) + clapper_reactables_manager_trigger_played_item_changed (player->reactables_manager, player->played_item); if (clapper_player_get_have_features (player)) clapper_features_manager_trigger_played_item_changed (player->features_manager, player->played_item); } diff --git a/src/lib/clapper/clapper-player-private.h b/src/lib/clapper/clapper-player-private.h index c042cbae..f201e34e 100644 --- a/src/lib/clapper/clapper-player-private.h +++ b/src/lib/clapper/clapper-player-private.h @@ -27,6 +27,7 @@ #include "clapper-app-bus-private.h" #include "clapper-features-manager-private.h" +#include "clapper-reactables-manager-private.h" G_BEGIN_DECLS @@ -47,6 +48,8 @@ struct _ClapperPlayer ClapperFeaturesManager *features_manager; gint have_features; // atomic integer + ClapperReactablesManager *reactables_manager; + ClapperEnhancerProxyList *enhancer_proxies; /* This is different from queue current item as it is used/changed only diff --git a/src/lib/clapper/clapper-player.c b/src/lib/clapper/clapper-player.c index de1efcc0..c4cb5eb6 100644 --- a/src/lib/clapper/clapper-player.c +++ b/src/lib/clapper/clapper-player.c @@ -50,6 +50,7 @@ #include "clapper-audio-stream-private.h" #include "clapper-subtitle-stream-private.h" #include "clapper-enhancer-proxy-list-private.h" +#include "clapper-reactable.h" #include "clapper-enums-private.h" #include "clapper-utils-private.h" #include "../shared/clapper-shared-utils-private.h" @@ -159,6 +160,8 @@ clapper_player_refresh_position (ClapperPlayer *self) clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_POSITION]); + if (self->reactables_manager) + clapper_reactables_manager_trigger_position_changed (self->reactables_manager, position_dbl); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_position_changed (self->features_manager, position_dbl); } @@ -225,6 +228,8 @@ clapper_player_handle_playbin_state_changed (ClapperPlayer *self) clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_STATE]); + if (self->reactables_manager) + clapper_reactables_manager_trigger_state_changed (self->reactables_manager, state); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_state_changed (self->features_manager, state); } @@ -256,6 +261,8 @@ clapper_player_handle_playbin_volume_changed (ClapperPlayer *self, const GValue clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_VOLUME]); + if (self->reactables_manager) + clapper_reactables_manager_trigger_volume_changed (self->reactables_manager, volume); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_volume_changed (self->features_manager, volume); } @@ -280,6 +287,8 @@ clapper_player_handle_playbin_mute_changed (ClapperPlayer *self, const GValue *v clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_MUTE]); + if (self->reactables_manager) + clapper_reactables_manager_trigger_mute_changed (self->reactables_manager, mute); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_mute_changed (self->features_manager, mute); } @@ -400,6 +409,8 @@ clapper_player_handle_playbin_rate_changed (ClapperPlayer *self, gdouble speed) clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_SPEED]); + if (self->reactables_manager) + clapper_reactables_manager_trigger_speed_changed (self->reactables_manager, speed); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_speed_changed (self->features_manager, speed); } @@ -2326,6 +2337,13 @@ clapper_player_init (ClapperPlayer *self) clapper_enhancer_proxy_list_fill_from_global_proxies (self->enhancer_proxies); + if (clapper_enhancer_proxy_list_has_proxy_with_interface (self->enhancer_proxies, CLAPPER_TYPE_REACTABLE)) { + self->reactables_manager = clapper_reactables_manager_new (); + gst_object_set_parent (GST_OBJECT_CAST (self->reactables_manager), GST_OBJECT_CAST (self)); + + clapper_reactables_manager_trigger_prepare (self->reactables_manager); + } + self->position_query = gst_query_new_position (GST_FORMAT_TIME); self->current_state = GST_STATE_NULL; @@ -2392,6 +2410,11 @@ clapper_player_finalize (GObject *object) gst_object_unparent (GST_OBJECT_CAST (self->subtitle_streams)); gst_object_unref (self->subtitle_streams); + if (self->reactables_manager) { + gst_object_unparent (GST_OBJECT_CAST (self->reactables_manager)); + gst_object_unref (self->reactables_manager); + } + gst_object_unparent (GST_OBJECT_CAST (self->enhancer_proxies)); gst_object_unref (self->enhancer_proxies); diff --git a/src/lib/clapper/clapper-queue.c b/src/lib/clapper/clapper-queue.c index 98f1dac5..837ac4f6 100644 --- a/src/lib/clapper/clapper-queue.c +++ b/src/lib/clapper/clapper-queue.c @@ -29,6 +29,8 @@ #include "clapper-media-item-private.h" #include "clapper-player-private.h" #include "clapper-playbin-bus-private.h" +#include "clapper-reactables-manager-private.h" +#include "clapper-features-manager-private.h" #define CLAPPER_QUEUE_GET_REC_LOCK(obj) (&CLAPPER_QUEUE_CAST(obj)->rec_lock) #define CLAPPER_QUEUE_REC_LOCK(obj) g_rec_mutex_lock (CLAPPER_QUEUE_GET_REC_LOCK(obj)) @@ -132,15 +134,27 @@ _announce_model_update (ClapperQueue *self, guint index, guint removed, guint ad if (removed != added) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); - if (player && clapper_player_get_have_features (player)) { - if (added == 1) // addition - clapper_features_manager_trigger_queue_item_added (player->features_manager, changed_item, index); - else if (removed == 1) // removal - clapper_features_manager_trigger_queue_item_removed (player->features_manager, changed_item, index); - else if (removed > 1 && added == 0) // queue cleared - clapper_features_manager_trigger_queue_cleared (player->features_manager); - else + if (player) { + gboolean have_features = clapper_player_get_have_features (player); + + if (added == 1) { // addition + if (player->reactables_manager) + clapper_reactables_manager_trigger_queue_item_added (player->reactables_manager, changed_item, index); + if (have_features) + clapper_features_manager_trigger_queue_item_added (player->features_manager, changed_item, index); + } else if (removed == 1) { // removal + if (player->reactables_manager) + clapper_reactables_manager_trigger_queue_item_removed (player->reactables_manager, changed_item, index); + if (have_features) + clapper_features_manager_trigger_queue_item_removed (player->features_manager, changed_item, index); + } else if (removed > 1 && added == 0) { // queue cleared + if (player->reactables_manager) + clapper_reactables_manager_trigger_queue_cleared (player->reactables_manager); + if (have_features) + clapper_features_manager_trigger_queue_cleared (player->features_manager); + } else { g_assert_not_reached (); + } } gst_clear_object (&player); @@ -160,6 +174,8 @@ _announce_reposition (ClapperQueue *self, guint before, guint after) GST_DEBUG_OBJECT (self, "Announcing item reposition: %u -> %u", before, after); if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) { + if (player->reactables_manager) + clapper_reactables_manager_trigger_queue_item_repositioned (player->reactables_manager, before, after); if (clapper_player_get_have_features (player)) clapper_features_manager_trigger_queue_item_repositioned (player->features_manager, before, after); @@ -989,6 +1005,8 @@ clapper_queue_set_progression_mode (ClapperQueue *self, ClapperQueueProgressionM clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_PROGRESSION_MODE]); + if (player->reactables_manager) + clapper_reactables_manager_trigger_queue_progression_changed (player->reactables_manager, mode); if (clapper_player_get_have_features (player)) clapper_features_manager_trigger_queue_progression_changed (player->features_manager, mode); diff --git a/src/lib/clapper/clapper-reactables-manager-private.h b/src/lib/clapper/clapper-reactables-manager-private.h new file mode 100644 index 00000000..80496648 --- /dev/null +++ b/src/lib/clapper/clapper-reactables-manager-private.h @@ -0,0 +1,82 @@ +/* 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 "clapper-enums.h" +#include "clapper-threaded-object.h" +#include "clapper-enhancer-proxy.h" +#include "clapper-media-item.h" + +G_BEGIN_DECLS + +#define CLAPPER_TYPE_REACTABLES_MANAGER (clapper_reactables_manager_get_type()) +#define CLAPPER_REACTABLES_MANAGER_CAST(obj) ((ClapperReactablesManager *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperReactablesManager, clapper_reactables_manager, CLAPPER, REACTABLES_MANAGER, ClapperThreadedObject) + +G_GNUC_INTERNAL +void clapper_reactables_manager_initialize (void); + +G_GNUC_INTERNAL +ClapperReactablesManager * clapper_reactables_manager_new (void); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_prepare (ClapperReactablesManager *manager); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_configure_take_config (ClapperReactablesManager *manager, ClapperEnhancerProxy *proxy, GstStructure *config); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_state_changed (ClapperReactablesManager *manager, ClapperPlayerState state); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_position_changed (ClapperReactablesManager *manager, gdouble position); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_speed_changed (ClapperReactablesManager *manager, gdouble speed); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_volume_changed (ClapperReactablesManager *manager, gdouble volume); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_mute_changed (ClapperReactablesManager *manager, gboolean mute); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_played_item_changed (ClapperReactablesManager *manager, ClapperMediaItem *item); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *manager, ClapperMediaItem *item); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_queue_item_added (ClapperReactablesManager *manager, ClapperMediaItem *item, guint index); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_queue_item_removed (ClapperReactablesManager *manager, ClapperMediaItem *item, guint index); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_queue_item_repositioned (ClapperReactablesManager *manager, guint before, guint after); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_queue_cleared (ClapperReactablesManager *manager); + +G_GNUC_INTERNAL +void clapper_reactables_manager_trigger_queue_progression_changed (ClapperReactablesManager *manager, ClapperQueueProgressionMode mode); + +G_END_DECLS diff --git a/src/lib/clapper/clapper-reactables-manager.c b/src/lib/clapper/clapper-reactables-manager.c new file mode 100644 index 00000000..8971deb6 --- /dev/null +++ b/src/lib/clapper/clapper-reactables-manager.c @@ -0,0 +1,533 @@ +/* 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. + */ + +#include + +#include "clapper-reactables-manager-private.h" +#include "clapper-reactable.h" +#include "clapper-bus-private.h" +#include "clapper-player.h" +#include "clapper-enhancer-proxy-list.h" +#include "clapper-enhancer-proxy-private.h" +#include "clapper-utils-private.h" + +#include "clapper-functionalities-availability.h" + +#if CLAPPER_WITH_ENHANCERS_LOADER +#include "clapper-enhancers-loader-private.h" +#endif + +#define CONFIG_STRUCTURE_NAME "config" + +#define GST_CAT_DEFAULT clapper_reactables_manager_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperReactablesManager +{ + ClapperThreadedObject parent; + + GstBus *bus; + GPtrArray *array; +}; + +#define parent_class clapper_reactables_manager_parent_class +G_DEFINE_TYPE (ClapperReactablesManager, clapper_reactables_manager, CLAPPER_TYPE_THREADED_OBJECT); + +typedef struct +{ + ClapperReactable *reactable; + ClapperEnhancerProxy *proxy; + GSettings *settings; +} ClapperReactableManagerData; + +enum +{ + CLAPPER_REACTABLES_MANAGER_EVENT_INVALID = 0, + CLAPPER_REACTABLES_MANAGER_EVENT_STATE_CHANGED, + CLAPPER_REACTABLES_MANAGER_EVENT_POSITION_CHANGED, + CLAPPER_REACTABLES_MANAGER_EVENT_SPEED_CHANGED, + CLAPPER_REACTABLES_MANAGER_EVENT_VOLUME_CHANGED, + CLAPPER_REACTABLES_MANAGER_EVENT_MUTE_CHANGED, + CLAPPER_REACTABLES_MANAGER_EVENT_PLAYED_ITEM_CHANGED, + CLAPPER_REACTABLES_MANAGER_EVENT_ITEM_UPDATED, + CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_ITEM_ADDED, + CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_ITEM_REMOVED, + CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED, + CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_CLEARED, + CLAPPER_REACTABLES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED +}; + +enum +{ + CLAPPER_REACTABLES_MANAGER_QUARK_PREPARE = 0, + CLAPPER_REACTABLES_MANAGER_QUARK_CONFIGURE, + CLAPPER_REACTABLES_MANAGER_QUARK_EVENT, + CLAPPER_REACTABLES_MANAGER_QUARK_VALUE, + CLAPPER_REACTABLES_MANAGER_QUARK_EXTRA_VALUE +}; + +static ClapperBusQuark _quarks[] = { + {"prepare", 0}, + {"configure", 0}, + {"event", 0}, + {"value", 0}, + {"extra-value", 0}, + {NULL, 0} +}; + +#define _EVENT(e) G_PASTE(CLAPPER_REACTABLES_MANAGER_EVENT_, e) +#define _QUARK(q) (_quarks[CLAPPER_REACTABLES_MANAGER_QUARK_##q].quark) + +#define _BUS_POST_EVENT_SINGLE(event_id,lower,type,val) { \ + GValue _value = G_VALUE_INIT; \ + g_value_init (&_value, type); \ + g_value_set_##lower (&_value, val); \ + _bus_post_event (self, event_id, &_value, NULL); } + +#define _BUS_POST_EVENT_DUAL(event_id,lower1,type1,val1,lower2,type2,val2) { \ + GValue _value1 = G_VALUE_INIT; \ + GValue _value2 = G_VALUE_INIT; \ + g_value_init (&_value1, type1); \ + g_value_init (&_value2, type2); \ + g_value_set_##lower1 (&_value1, val1); \ + g_value_set_##lower2 (&_value2, val2); \ + _bus_post_event (self, event_id, &_value1, &_value2); } + +void +clapper_reactables_manager_initialize (void) +{ + gint i; + + for (i = 0; _quarks[i].name; ++i) + _quarks[i].quark = g_quark_from_static_string (_quarks[i].name); +} + +static void +_settings_changed_cb (GSettings *settings, const gchar *key, ClapperReactableManagerData *data) +{ + GST_DEBUG_OBJECT (data->reactable, "Global setting \"%s\" changed", key); + + /* Local settings are applied through bus events, so all that is + * needed here is a check to not overwrite locally set setting */ + if (!clapper_enhancer_proxy_has_locally_set (data->proxy, key)) { + GVariant *variant = g_settings_get_value (settings, key); + GValue value = G_VALUE_INIT; + + if (G_LIKELY (clapper_utils_set_value_from_variant (&value, variant))) { + g_object_set_property (G_OBJECT (data->reactable), key, &value); + g_value_unset (&value); + } + + g_variant_unref (variant); + } +} + +static inline void +clapper_reactables_manager_handle_prepare (ClapperReactablesManager *self) +{ + ClapperPlayer *player; + + GST_INFO_OBJECT (self, "Preparing reactable enhancers"); + player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (self))); + + if (G_LIKELY (player != NULL)) { + ClapperEnhancerProxyList *proxies = clapper_player_get_enhancer_proxies (player); + 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); + ClapperReactable *reactable = NULL; + + if (!clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_REACTABLE)) + continue; + +#if CLAPPER_WITH_ENHANCERS_LOADER + reactable = CLAPPER_REACTABLE_CAST ( + clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_REACTABLE)); +#endif + + if (G_LIKELY (reactable != NULL)) { + ClapperReactableManagerData *data; + GstStructure *config; + + if (g_object_is_floating (reactable)) + gst_object_ref_sink (reactable); + + data = g_new (ClapperReactableManagerData, 1); + data->reactable = reactable; + data->proxy = gst_object_ref (proxy); + data->settings = clapper_enhancer_proxy_get_settings (proxy); + + GST_TRACE_OBJECT (self, "Created data for reactable: %" GST_PTR_FORMAT, data->reactable); + + /* Settings are stored in data in order for this signal to keep working */ + if (data->settings) + g_signal_connect (data->settings, "changed", G_CALLBACK (_settings_changed_cb), data); + + if ((config = clapper_enhancer_proxy_make_current_config (proxy))) { + clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) reactable); + gst_structure_free (config); + } + + g_ptr_array_add (self->array, data); + gst_object_set_parent (GST_OBJECT_CAST (data->reactable), GST_OBJECT_CAST (player)); + } + } + + GST_INFO_OBJECT (self, "Prepared %i reactable enhancers", self->array->len); + gst_object_unref (player); + } else { + GST_ERROR_OBJECT (self, "Could not prepare reactable enhancers!"); + } +} + +static inline void +clapper_reactables_manager_handle_configure (ClapperReactablesManager *self, const GstStructure *structure) +{ + const GValue *proxy_val, *config_val; + ClapperEnhancerProxy *proxy; + const GstStructure *config; + guint i; + + proxy_val = gst_structure_id_get_value (structure, _QUARK (VALUE)); + config_val = gst_structure_id_get_value (structure, _QUARK (EXTRA_VALUE)); + + proxy = CLAPPER_ENHANCER_PROXY_CAST (g_value_get_object (proxy_val)); + config = gst_value_get_structure (config_val); + + for (i = 0; i < self->array->len; ++i) { + ClapperReactableManagerData *data = g_ptr_array_index (self->array, i); + + if (data->proxy == proxy) { + clapper_enhancer_proxy_apply_config_to_enhancer (data->proxy, + config, (GObject *) data->reactable); + return; + } + } + + GST_ERROR_OBJECT (self, "Triggered configure, but no matching enhancer proxy found"); +} + +static inline void +clapper_reactables_manager_handle_event (ClapperReactablesManager *self, const GstStructure *structure) +{ + const GValue *value = gst_structure_id_get_value (structure, _QUARK (VALUE)); + const GValue *extra_value = gst_structure_id_get_value (structure, _QUARK (EXTRA_VALUE)); + guint i, event_id; + + if (G_UNLIKELY (!gst_structure_id_get (structure, + _QUARK (EVENT), G_TYPE_ENUM, &event_id, NULL))) { + GST_ERROR_OBJECT (self, "Could not read event ID"); + return; + } + + for (i = 0; i < self->array->len; ++i) { + ClapperReactableManagerData *data = g_ptr_array_index (self->array, i); + ClapperReactableInterface *reactable_iface = CLAPPER_REACTABLE_GET_IFACE (data->reactable); + + switch (event_id) { + case _EVENT (STATE_CHANGED): + if (reactable_iface->state_changed) + reactable_iface->state_changed (data->reactable, g_value_get_int (value)); + break; + case _EVENT (POSITION_CHANGED): + if (reactable_iface->position_changed) + reactable_iface->position_changed (data->reactable, g_value_get_double (value)); + break; + case _EVENT (SPEED_CHANGED): + if (reactable_iface->speed_changed) + reactable_iface->speed_changed (data->reactable, g_value_get_double (value)); + break; + case _EVENT (VOLUME_CHANGED): + if (reactable_iface->volume_changed) + reactable_iface->volume_changed (data->reactable, g_value_get_double (value)); + break; + case _EVENT (MUTE_CHANGED): + if (reactable_iface->mute_changed) + reactable_iface->mute_changed (data->reactable, g_value_get_boolean (value)); + break; + case _EVENT (PLAYED_ITEM_CHANGED): + if (reactable_iface->played_item_changed) { + reactable_iface->played_item_changed (data->reactable, + CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value))); + } + break; + case _EVENT (ITEM_UPDATED): + if (reactable_iface->item_updated) { + reactable_iface->item_updated (data->reactable, + CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value))); + } + break; + case _EVENT (QUEUE_ITEM_ADDED): + if (reactable_iface->queue_item_added) { + reactable_iface->queue_item_added (data->reactable, + CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)), + g_value_get_uint (extra_value)); + } + break; + case _EVENT (QUEUE_ITEM_REMOVED): + if (reactable_iface->queue_item_removed) { + reactable_iface->queue_item_removed (data->reactable, + CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)), + g_value_get_uint (extra_value)); + } + break; + case _EVENT (QUEUE_ITEM_REPOSITIONED): + if (reactable_iface->queue_item_repositioned) { + reactable_iface->queue_item_repositioned (data->reactable, + g_value_get_uint (value), + g_value_get_uint (extra_value)); + } + break; + case _EVENT (QUEUE_CLEARED): + if (reactable_iface->queue_cleared) + reactable_iface->queue_cleared (data->reactable); + break; + case _EVENT (QUEUE_PROGRESSION_CHANGED): + if (reactable_iface->queue_progression_changed) + reactable_iface->queue_progression_changed (data->reactable, g_value_get_int (value)); + break; + default: + GST_ERROR_OBJECT (self, "Invalid event ID on reactables bus: %u", event_id); + break; + } + } +} + +static gboolean +_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED) +{ + if (G_LIKELY (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_APPLICATION)) { + ClapperReactablesManager *self = CLAPPER_REACTABLES_MANAGER_CAST (GST_MESSAGE_SRC (msg)); + const GstStructure *structure = gst_message_get_structure (msg); + GQuark quark = gst_structure_get_name_id (structure); + + if (quark == _QUARK (EVENT)) { + clapper_reactables_manager_handle_event (self, structure); + } else if (quark == _QUARK (PREPARE)) { + clapper_reactables_manager_handle_prepare (self); + } else if (quark == _QUARK (CONFIGURE)) { + clapper_reactables_manager_handle_configure (self, structure); + } else { + GST_ERROR_OBJECT (self, "Received invalid quark on reactables bus!"); + } + } + + return G_SOURCE_CONTINUE; +} + +static void +_bus_post_event (ClapperReactablesManager *self, guint event_id, + GValue *value, GValue *extra_value) +{ + GstStructure *structure = gst_structure_new_id (_QUARK (EVENT), + _QUARK (EVENT), G_TYPE_ENUM, event_id, + NULL); + + if (value) + gst_structure_id_take_value (structure, _QUARK (VALUE), value); + if (extra_value) + gst_structure_id_take_value (structure, _QUARK (EXTRA_VALUE), extra_value); + + gst_bus_post (self->bus, gst_message_new_application ( + GST_OBJECT_CAST (self), structure)); +} + +/* + * clapper_reactables_manager_new: + * + * Returns: (transfer full): a new #ClapperReactablesManager instance. + */ +ClapperReactablesManager * +clapper_reactables_manager_new (void) +{ + ClapperReactablesManager *reactables_manager; + + reactables_manager = g_object_new (CLAPPER_TYPE_REACTABLES_MANAGER, NULL); + gst_object_ref_sink (reactables_manager); + + return reactables_manager; +} + +void +clapper_reactables_manager_trigger_prepare (ClapperReactablesManager *self) +{ + GstStructure *structure = gst_structure_new_id_empty (_QUARK (PREPARE)); + + gst_bus_post (self->bus, gst_message_new_application ( + GST_OBJECT_CAST (self), structure)); +} + +void +clapper_reactables_manager_trigger_configure_take_config (ClapperReactablesManager *self, + ClapperEnhancerProxy *proxy, GstStructure *config) +{ + GstStructure *structure = gst_structure_new_id (_QUARK (CONFIGURE), + _QUARK (VALUE), G_TYPE_OBJECT, proxy, NULL); + GValue extra_value = G_VALUE_INIT; + + g_value_init (&extra_value, GST_TYPE_STRUCTURE); + g_value_take_boxed (&extra_value, config); + + gst_structure_id_take_value (structure, _QUARK (EXTRA_VALUE), &extra_value); + + gst_bus_post (self->bus, gst_message_new_application ( + GST_OBJECT_CAST (self), structure)); +} + +void +clapper_reactables_manager_trigger_state_changed (ClapperReactablesManager *self, ClapperPlayerState state) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (STATE_CHANGED), int, G_TYPE_INT, state); +} + +void +clapper_reactables_manager_trigger_position_changed (ClapperReactablesManager *self, gdouble position) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (POSITION_CHANGED), double, G_TYPE_DOUBLE, position); +} + +void +clapper_reactables_manager_trigger_speed_changed (ClapperReactablesManager *self, gdouble speed) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (SPEED_CHANGED), double, G_TYPE_DOUBLE, speed); +} + +void +clapper_reactables_manager_trigger_volume_changed (ClapperReactablesManager *self, gdouble volume) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (VOLUME_CHANGED), double, G_TYPE_DOUBLE, volume); +} + +void +clapper_reactables_manager_trigger_mute_changed (ClapperReactablesManager *self, gboolean mute) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (MUTE_CHANGED), boolean, G_TYPE_BOOLEAN, mute); +} + +void +clapper_reactables_manager_trigger_played_item_changed (ClapperReactablesManager *self, ClapperMediaItem *item) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (PLAYED_ITEM_CHANGED), object, CLAPPER_TYPE_MEDIA_ITEM, item); +} + +void +clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *self, ClapperMediaItem *item) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (ITEM_UPDATED), object, CLAPPER_TYPE_MEDIA_ITEM, item); +} + +void +clapper_reactables_manager_trigger_queue_item_added (ClapperReactablesManager *self, ClapperMediaItem *item, guint index) +{ + _BUS_POST_EVENT_DUAL (_EVENT (QUEUE_ITEM_ADDED), object, CLAPPER_TYPE_MEDIA_ITEM, item, uint, G_TYPE_UINT, index); +} + +void +clapper_reactables_manager_trigger_queue_item_removed (ClapperReactablesManager *self, ClapperMediaItem *item, guint index) +{ + _BUS_POST_EVENT_DUAL (_EVENT (QUEUE_ITEM_REMOVED), object, CLAPPER_TYPE_MEDIA_ITEM, item, uint, G_TYPE_UINT, index); +} + +void +clapper_reactables_manager_trigger_queue_item_repositioned (ClapperReactablesManager *self, guint before, guint after) +{ + _BUS_POST_EVENT_DUAL (_EVENT (QUEUE_ITEM_REPOSITIONED), uint, G_TYPE_UINT, before, uint, G_TYPE_UINT, after); +} + +void +clapper_reactables_manager_trigger_queue_cleared (ClapperReactablesManager *self) +{ + _bus_post_event (self, _EVENT (QUEUE_CLEARED), NULL, NULL); +} + +void +clapper_reactables_manager_trigger_queue_progression_changed (ClapperReactablesManager *self, ClapperQueueProgressionMode mode) +{ + _BUS_POST_EVENT_SINGLE (_EVENT (QUEUE_PROGRESSION_CHANGED), int, G_TYPE_INT, mode); +} + +static void +_data_remove_func (ClapperReactableManagerData *data) +{ + GST_TRACE ("Removing data for reactable: %" GST_PTR_FORMAT, data->reactable); + + g_clear_object (&data->settings); + + gst_object_unparent (GST_OBJECT_CAST (data->reactable)); + gst_object_unref (data->reactable); + + gst_object_unref (data->proxy); + g_free (data); +} + +static void +clapper_reactables_manager_thread_start (ClapperThreadedObject *threaded_object) +{ + ClapperReactablesManager *self = CLAPPER_REACTABLES_MANAGER_CAST (threaded_object); + + GST_TRACE_OBJECT (threaded_object, "Reactables manager thread start"); + + self->array = g_ptr_array_new_with_free_func ( + (GDestroyNotify) _data_remove_func); + + self->bus = gst_bus_new (); + gst_bus_add_watch (self->bus, (GstBusFunc) _bus_message_func, NULL); +} + +static void +clapper_reactables_manager_thread_stop (ClapperThreadedObject *threaded_object) +{ + ClapperReactablesManager *self = CLAPPER_REACTABLES_MANAGER_CAST (threaded_object); + + GST_TRACE_OBJECT (self, "Reactables manager thread stop"); + + gst_bus_set_flushing (self->bus, TRUE); + gst_bus_remove_watch (self->bus); + gst_clear_object (&self->bus); + + g_ptr_array_unref (self->array); +} + +static void +clapper_reactables_manager_init (ClapperReactablesManager *self) +{ +} + +static void +clapper_reactables_manager_finalize (GObject *object) +{ + GST_TRACE_OBJECT (object, "Finalize"); + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_reactables_manager_class_init (ClapperReactablesManagerClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperreactablesmanager", 0, + "Clapper Reactables Manager"); + + gobject_class->finalize = clapper_reactables_manager_finalize; + + threaded_object->thread_start = clapper_reactables_manager_thread_start; + threaded_object->thread_stop = clapper_reactables_manager_thread_stop; +} diff --git a/src/lib/clapper/clapper-timeline.c b/src/lib/clapper/clapper-timeline.c index 7bd911a7..2b4c9252 100644 --- a/src/lib/clapper/clapper-timeline.c +++ b/src/lib/clapper/clapper-timeline.c @@ -29,6 +29,8 @@ #include "clapper-timeline-private.h" #include "clapper-marker-private.h" #include "clapper-player-private.h" +#include "clapper-reactables-manager-private.h" +#include "clapper-features-manager-private.h" #define GST_CAT_DEFAULT clapper_timeline_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); @@ -106,15 +108,17 @@ clapper_timeline_post_item_updated (ClapperTimeline *self) ClapperPlayer *player; if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) { - ClapperFeaturesManager *features_manager; + ClapperMediaItem *item; - if ((features_manager = clapper_player_get_features_manager (player))) { - ClapperMediaItem *item; + if ((item = CLAPPER_MEDIA_ITEM_CAST (gst_object_get_parent (GST_OBJECT_CAST (self))))) { + ClapperFeaturesManager *features_manager; - if ((item = CLAPPER_MEDIA_ITEM (gst_object_get_parent (GST_OBJECT_CAST (self))))) { + if (player->reactables_manager) + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, item); + if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, item); - gst_object_unref (item); - } + + gst_object_unref (item); } gst_object_unref (player); diff --git a/src/lib/clapper/clapper-utils-private.h b/src/lib/clapper/clapper-utils-private.h index 8e402a6a..fc59fec7 100644 --- a/src/lib/clapper/clapper-utils-private.h +++ b/src/lib/clapper/clapper-utils-private.h @@ -54,4 +54,7 @@ gchar * clapper_utils_uri_from_file (GFile *file); G_GNUC_INTERNAL gchar * clapper_utils_title_from_uri (const gchar *uri); +G_GNUC_INTERNAL +gboolean clapper_utils_set_value_from_variant (GValue *value, GVariant *variant); + G_END_DECLS diff --git a/src/lib/clapper/clapper-utils.c b/src/lib/clapper/clapper-utils.c index 86db8e90..39979907 100644 --- a/src/lib/clapper/clapper-utils.c +++ b/src/lib/clapper/clapper-utils.c @@ -271,3 +271,59 @@ clapper_utils_title_from_uri (const gchar *uri) return title; } + +gboolean +clapper_utils_set_value_from_variant (GValue *value, GVariant *variant) +{ + const gchar *var_type = g_variant_get_type_string (variant); + GType val_type; + + switch (var_type[0]) { + case 'b': + val_type = G_TYPE_BOOLEAN; + break; + case 'i': + val_type = G_TYPE_INT; + break; + case 'u': + val_type = G_TYPE_UINT; + break; + case 'd': + val_type = G_TYPE_DOUBLE; + break; + case 's': + val_type = G_TYPE_STRING; + break; + default: + goto error; + } + + g_value_init (value, val_type); + + switch (val_type) { + case G_TYPE_BOOLEAN: + g_value_set_boolean (value, g_variant_get_boolean (variant)); + break; + case G_TYPE_INT: + g_value_set_int (value, g_variant_get_int32 (variant)); + break; + case G_TYPE_UINT: + g_value_set_uint (value, g_variant_get_uint32 (variant)); + break; + case G_TYPE_DOUBLE: + g_value_set_double (value, g_variant_get_double (variant)); + break; + case G_TYPE_STRING: + g_value_set_string (value, g_variant_get_string (variant, NULL)); + break; + default: + g_assert_not_reached (); + break; + } + + return TRUE; + +error: + GST_ERROR ("Unsupported conversion for variant type: %s", var_type); + return FALSE; +} diff --git a/src/lib/clapper/gst/clapper-plugin.c b/src/lib/clapper/gst/clapper-plugin.c index 2bb2a7d9..ba7a1b72 100644 --- a/src/lib/clapper/gst/clapper-plugin.c +++ b/src/lib/clapper/gst/clapper-plugin.c @@ -23,36 +23,13 @@ #include "../clapper-basic-functions.h" #include "../clapper-enhancer-proxy.h" -#include "../clapper-enhancer-proxy-list.h" +#include "../clapper-enhancer-proxy-list-private.h" #include "../clapper-extractable.h" #include "clapper-plugin-private.h" #include "clapper-extractable-src-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) { @@ -66,7 +43,7 @@ clapper_gst_plugin_init (GstPlugin *plugin) global_proxies = clapper_get_global_enhancer_proxies (); /* Avoid registering an URI handler without schemes */ - if (clapper_gst_plugin_has_enhancers (global_proxies, CLAPPER_TYPE_EXTRACTABLE)) + if (clapper_enhancer_proxy_list_has_proxy_with_interface (global_proxies, CLAPPER_TYPE_EXTRACTABLE)) res |= GST_ELEMENT_REGISTER (clapperextractablesrc, plugin); res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin); diff --git a/src/lib/clapper/meson.build b/src/lib/clapper/meson.build index 522f00cd..655ea4df 100644 --- a/src/lib/clapper/meson.build +++ b/src/lib/clapper/meson.build @@ -149,6 +149,7 @@ clapper_sources = [ 'clapper-player.c', 'clapper-queue.c', 'clapper-reactable.c', + 'clapper-reactables-manager.c', 'clapper-stream.c', 'clapper-stream-list.c', 'clapper-subtitle-stream.c', From c0b360dc0fff65477d9ec171e4c25fb9d37f1f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Mon, 9 Jun 2025 18:37:40 +0200 Subject: [PATCH 3/4] clapper-app: Add support for MPRIS enhancer --- src/bin/clapper-app/clapper-app-window.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/bin/clapper-app/clapper-app-window.c b/src/bin/clapper-app/clapper-app-window.c index 9157b1a2..792ae82b 100644 --- a/src/bin/clapper-app/clapper-app-window.c +++ b/src/bin/clapper-app/clapper-app-window.c @@ -86,9 +86,7 @@ typedef struct gint64 last_tick; } ClapperAppWindowResizeData; -#if CLAPPER_HAVE_MPRIS static guint16 instance_count = 0; -#endif static inline GQuark clapper_app_window_extra_options_get_quark (void) @@ -1252,16 +1250,28 @@ clapper_app_window_constructed (GObject *object) #if (CLAPPER_HAVE_MPRIS || CLAPPER_HAVE_SERVER || CLAPPER_HAVE_DISCOVERER) ClapperFeature *feature = NULL; #endif -#if CLAPPER_HAVE_MPRIS +#if (!CLAPPER_HAVE_MPRIS || !CLAPPER_HAVE_SERVER || !CLAPPER_HAVE_DISCOVERER) + ClapperEnhancerProxyList *proxies = clapper_player_get_enhancer_proxies (player); + ClapperEnhancerProxy *proxy; +#endif + gchar mpris_name[45]; g_snprintf (mpris_name, sizeof (mpris_name), "org.mpris.MediaPlayer2.Clapper.instance%" G_GUINT16_FORMAT, instance_count++); -#endif self->settings = g_settings_new (CLAPPER_APP_ID); self->last_volume = PERCENTAGE_ROUND (g_settings_get_double (self->settings, "volume")); -#if CLAPPER_HAVE_MPRIS +#if !CLAPPER_HAVE_MPRIS + if ((proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, "clapper-mpris"))) { + clapper_enhancer_proxy_set_locally (proxy, + "own-name", mpris_name, + "identity", CLAPPER_APP_NAME, + "desktop-entry", CLAPPER_APP_ID, + "queue-controllable", TRUE, NULL); + gst_object_unref (proxy); + } +#else feature = CLAPPER_FEATURE (clapper_mpris_new ( mpris_name, CLAPPER_APP_NAME, CLAPPER_APP_ID)); clapper_mpris_set_queue_controllable (CLAPPER_MPRIS (feature), TRUE); From c354d314367a6fcafaa5dd5cf6247b51882f1dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= Date: Mon, 9 Jun 2025 19:54:30 +0200 Subject: [PATCH 4/4] clapper: reactable: Inform which properties were updated --- src/lib/clapper/clapper-enums.h | 19 ++++++++++++ src/lib/clapper/clapper-media-item.c | 30 ++++++++++++------- src/lib/clapper/clapper-playbin-bus.c | 6 ++-- src/lib/clapper/clapper-reactable.h | 3 +- .../clapper-reactables-manager-private.h | 2 +- src/lib/clapper/clapper-reactables-manager.c | 7 +++-- src/lib/clapper/clapper-timeline.c | 6 ++-- 7 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/lib/clapper/clapper-enums.h b/src/lib/clapper/clapper-enums.h index 70dc5841..2140e2e9 100644 --- a/src/lib/clapper/clapper-enums.h +++ b/src/lib/clapper/clapper-enums.h @@ -151,4 +151,23 @@ typedef enum CLAPPER_ENHANCER_PARAM_DIRPATH = 1 << 20, } ClapperEnhancerParamFlags; +/** + * ClapperReactableItemUpdatedFlags: + * @CLAPPER_REACTABLE_ITEM_UPDATED_TITLE: Media item title was updated. + * @CLAPPER_REACTABLE_ITEM_UPDATED_DURATION: Media item duration was updated. + * @CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE: Media item timeline was updated. + * @CLAPPER_REACTABLE_ITEM_UPDATED_TAGS: Media item tags were updated. + * + * Flags informing which properties were updated within [class@Clapper.MediaItem]. + * + * Since: 0.10 + */ +typedef enum +{ + CLAPPER_REACTABLE_ITEM_UPDATED_TITLE = 1 << 0, + CLAPPER_REACTABLE_ITEM_UPDATED_DURATION = 1 << 1, + CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE = 1 << 2, + CLAPPER_REACTABLE_ITEM_UPDATED_TAGS = 1 << 3, +} ClapperReactableItemUpdatedFlags; + G_END_DECLS diff --git a/src/lib/clapper/clapper-media-item.c b/src/lib/clapper/clapper-media-item.c index b4e925c7..522e2f59 100644 --- a/src/lib/clapper/clapper-media-item.c +++ b/src/lib/clapper/clapper-media-item.c @@ -471,7 +471,7 @@ _tags_replace_func (const GstTagList *tags, const gchar *tag, ClapperMediaItemTa static gboolean clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagList *tags, - ClapperAppBus *app_bus, gboolean from_user) + ClapperAppBus *app_bus, gboolean from_user, ClapperReactableItemUpdatedFlags *flags) { ClapperMediaItemTagIterData data; gboolean title_changed = FALSE, cont_changed = FALSE; @@ -486,8 +486,12 @@ clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagLis gst_tag_list_foreach (tags, (GstTagForeachFunc) _tags_replace_func, &data); if (data.changed) { - title_changed = _refresh_tag_prop_unlocked (self, GST_TAG_TITLE, - from_user, &self->title); + *flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TAGS; + + if ((title_changed = _refresh_tag_prop_unlocked (self, GST_TAG_TITLE, + from_user, &self->title))) { + *flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TITLE; + } cont_changed = _refresh_tag_prop_unlocked (self, GST_TAG_CONTAINER_FORMAT, from_user, &self->container_format); } @@ -548,6 +552,7 @@ clapper_media_item_populate_tags (ClapperMediaItem *self, const GstTagList *tags { ClapperPlayer *player; ClapperAppBus *app_bus = NULL; + ClapperReactableItemUpdatedFlags flags = 0; gboolean changed; g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), FALSE); @@ -561,13 +566,13 @@ clapper_media_item_populate_tags (ClapperMediaItem *self, const GstTagList *tags if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) app_bus = player->app_bus; - changed = clapper_media_item_insert_tags_internal (self, tags, app_bus, TRUE); + changed = clapper_media_item_insert_tags_internal (self, tags, app_bus, TRUE, &flags); if (changed && player) { ClapperFeaturesManager *features_manager; if (player->reactables_manager) - clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self); + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } @@ -600,13 +605,14 @@ clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagLis GstTagScope scope = gst_tag_list_get_scope (tags); if (scope == GST_TAG_SCOPE_GLOBAL) { - gboolean changed = clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE); + ClapperReactableItemUpdatedFlags flags = 0; + gboolean changed = clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags); if (changed) { ClapperFeaturesManager *features_manager; if (player->reactables_manager) - clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self); + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } @@ -619,6 +625,7 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco ClapperPlayer *player; GstDiscovererStreamInfo *sinfo; GstClockTime duration; + ClapperReactableItemUpdatedFlags flags = 0; gdouble val_dbl; gboolean changed = FALSE; @@ -634,7 +641,7 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco GstDiscovererContainerInfo *cinfo = (GstDiscovererContainerInfo *) sinfo; if ((tags = gst_discoverer_container_info_get_tags (cinfo))) - changed |= clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE); + changed |= clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags); } gst_discoverer_stream_info_unref (sinfo); } @@ -645,13 +652,16 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco duration = 0; val_dbl = (gdouble) duration / GST_SECOND; - changed |= clapper_media_item_set_duration (self, val_dbl, player->app_bus); + if (clapper_media_item_set_duration (self, val_dbl, player->app_bus)) { + changed = TRUE; + flags |= CLAPPER_REACTABLE_ITEM_UPDATED_DURATION; + } if (changed) { ClapperFeaturesManager *features_manager; if (player->reactables_manager) - clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self); + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags); if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } diff --git a/src/lib/clapper/clapper-playbin-bus.c b/src/lib/clapper/clapper-playbin-bus.c index 0260455b..05746c18 100644 --- a/src/lib/clapper/clapper-playbin-bus.c +++ b/src/lib/clapper/clapper-playbin-bus.c @@ -173,8 +173,10 @@ _update_current_duration (ClapperPlayer *player) if (clapper_media_item_set_duration (player->played_item, duration_dbl, player->app_bus)) { ClapperFeaturesManager *features_manager; - if (player->reactables_manager) - clapper_reactables_manager_trigger_item_updated (player->reactables_manager, player->played_item); + if (player->reactables_manager) { + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, player->played_item, + CLAPPER_REACTABLE_ITEM_UPDATED_DURATION); + } if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, player->played_item); } diff --git a/src/lib/clapper/clapper-reactable.h b/src/lib/clapper/clapper-reactable.h index e53f2f8a..1ce1e810 100644 --- a/src/lib/clapper/clapper-reactable.h +++ b/src/lib/clapper/clapper-reactable.h @@ -130,6 +130,7 @@ struct _ClapperReactableInterface * ClapperReactableInterface::item_updated: * @reactable: a #ClapperReactable * @item: a #ClapperMediaItem that was updated + * @flags: flags informing which properties were updated * * An item in queue got updated. * @@ -140,7 +141,7 @@ struct _ClapperReactableInterface * * Since: 0.10 */ - void (* item_updated) (ClapperReactable *reactable, ClapperMediaItem *item); + void (* item_updated) (ClapperReactable *reactable, ClapperMediaItem *item, ClapperReactableItemUpdatedFlags flags); /** * ClapperReactableInterface::queue_item_added: diff --git a/src/lib/clapper/clapper-reactables-manager-private.h b/src/lib/clapper/clapper-reactables-manager-private.h index 80496648..1bef8f2c 100644 --- a/src/lib/clapper/clapper-reactables-manager-private.h +++ b/src/lib/clapper/clapper-reactables-manager-private.h @@ -62,7 +62,7 @@ G_GNUC_INTERNAL void clapper_reactables_manager_trigger_played_item_changed (ClapperReactablesManager *manager, ClapperMediaItem *item); G_GNUC_INTERNAL -void clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *manager, ClapperMediaItem *item); +void clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *manager, ClapperMediaItem *item, ClapperReactableItemUpdatedFlags flags); G_GNUC_INTERNAL void clapper_reactables_manager_trigger_queue_item_added (ClapperReactablesManager *manager, ClapperMediaItem *item, guint index); diff --git a/src/lib/clapper/clapper-reactables-manager.c b/src/lib/clapper/clapper-reactables-manager.c index 8971deb6..a527456a 100644 --- a/src/lib/clapper/clapper-reactables-manager.c +++ b/src/lib/clapper/clapper-reactables-manager.c @@ -271,7 +271,8 @@ clapper_reactables_manager_handle_event (ClapperReactablesManager *self, const G case _EVENT (ITEM_UPDATED): if (reactable_iface->item_updated) { reactable_iface->item_updated (data->reactable, - CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value))); + CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)), + g_value_get_flags (extra_value)); } break; case _EVENT (QUEUE_ITEM_ADDED): @@ -428,9 +429,9 @@ clapper_reactables_manager_trigger_played_item_changed (ClapperReactablesManager } void -clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *self, ClapperMediaItem *item) +clapper_reactables_manager_trigger_item_updated (ClapperReactablesManager *self, ClapperMediaItem *item, ClapperReactableItemUpdatedFlags _flags) { - _BUS_POST_EVENT_SINGLE (_EVENT (ITEM_UPDATED), object, CLAPPER_TYPE_MEDIA_ITEM, item); + _BUS_POST_EVENT_DUAL (_EVENT (ITEM_UPDATED), object, CLAPPER_TYPE_MEDIA_ITEM, item, flags, CLAPPER_TYPE_REACTABLE_ITEM_UPDATED_FLAGS, _flags); } void diff --git a/src/lib/clapper/clapper-timeline.c b/src/lib/clapper/clapper-timeline.c index 2b4c9252..3642736b 100644 --- a/src/lib/clapper/clapper-timeline.c +++ b/src/lib/clapper/clapper-timeline.c @@ -113,8 +113,10 @@ clapper_timeline_post_item_updated (ClapperTimeline *self) if ((item = CLAPPER_MEDIA_ITEM_CAST (gst_object_get_parent (GST_OBJECT_CAST (self))))) { ClapperFeaturesManager *features_manager; - if (player->reactables_manager) - clapper_reactables_manager_trigger_item_updated (player->reactables_manager, item); + if (player->reactables_manager) { + clapper_reactables_manager_trigger_item_updated (player->reactables_manager, item, + CLAPPER_REACTABLE_ITEM_UPDATED_TIMELINE); + } if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, item);