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',