clapper: Add "Reactable" interface

An interface for creating enhancers that react to the
playback and/or events that should influence it.
This commit is contained in:
Rafał Dzięgiel
2025-05-22 18:25:38 +02:00
parent 6273446817
commit 976bcc338f
6 changed files with 444 additions and 1 deletions

View File

@@ -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"

View File

@@ -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)
/**

View File

@@ -0,0 +1,210 @@
/* Clapper Playback Library
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* 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);
});
}

View File

@@ -0,0 +1,228 @@
/* Clapper Playback Library
* Copyright (C) 2025 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* 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 <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-visibility.h>
#include <clapper/clapper-player.h>
#include <clapper/clapper-enums.h>
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

View File

@@ -45,6 +45,7 @@
#include <clapper/clapper-video-stream.h>
#include <clapper/clapper-extractable.h>
#include <clapper/clapper-reactable.h>
#include <clapper/clapper-functionalities-availability.h>
#include <clapper/features/clapper-features-availability.h>

View File

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