18 Commits

Author SHA1 Message Date
Rafał Dzięgiel
c354d31436 clapper: reactable: Inform which properties were updated 2025-06-09 20:21:39 +02:00
Rafał Dzięgiel
c0b360dc0f clapper-app: Add support for MPRIS enhancer 2025-06-09 18:37:40 +02:00
Rafał Dzięgiel
a6ca0b726c 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.
2025-06-09 18:36:06 +02:00
Rafał Dzięgiel
976bcc338f clapper: Add "Reactable" interface
An interface for creating enhancers that react to the
playback and/or events that should influence it.
2025-06-08 17:46:35 +02:00
Rafał Dzięgiel
6273446817 Merge pull request #558 from Rafostar/tagged
clapper: Add taglist to media items
2025-06-08 17:43:12 +02:00
Rafał Dzięgiel
72ab32d4ef shared: Do not print deprecations when compiling own code
We keep and use old functions in code for the compatibility reasons with
older API versions. Do not print warnings about them being deprecated when
compiling Clapper library that has and uses such function internally.
2025-06-08 17:17:23 +02:00
Rafał Dzięgiel
e9d0d8f345 clapper: Add taglist to media items
Allow apps to read and/or populate initial taglist within media item.
Apps might care about other tags that Clapper application does not,
so this single property allows them to read whatever tag they might need.
2025-06-08 17:17:13 +02:00
Rafał Dzięgiel
0b8d359844 meson: Improve GIR init section
Init with disabled registry and remove it only for clapper-gtk where its not needed.
2025-06-08 16:57:04 +02:00
Rafał Dzięgiel
4a93bea203 Revert "meson: Remove GIR init section"
This reverts commit b05f0f2b30.
2025-06-08 16:56:57 +02:00
Rafał Dzięgiel
5e2c1a8e30 flatpak: Sync with Flathub 2025-06-08 16:40:47 +02:00
Rafał Dzięgiel
72c8e4ab84 clapper: Fix missing pspec ref when copying proxy
Newly created enhancer proxies hold param specs with a reference
on each and unref them when finalized. For this reason, copied
proxy objects needs to ref pspecs from source, otherwise it would
do an unref without holding a reference on object during destruction.
2025-06-02 20:10:53 +02:00
Rafał Dzięgiel
db61b9c773 gst-plugin: Avoid main thread invoke when used with "ClapperGtkVideo"
This thread invoke is done mainly to support testing with gst-launch-1.0,
otherwise no need when used with "ClapperGtkVideo". We can avoid doing this,
by checking whether this type was already registered in which case it means
that "ClapperGtkVideo" widget is used within GTK application and registered
before sink starts processing data.

In case of "ClapperGtkVideo" we might run into situation where these two threads
are stuck waiting for each other to be idle. This change works around this issue.

Fixes #555
2025-05-25 15:06:32 +02:00
Rafał Dzięgiel
682ad6c3c8 clapper: Allow peeking in Vala
Vala does better job at handling objects without increased reference than
interpreted languages, so its safe to expose list "peek" functions to it.
2025-05-23 16:15:11 +02:00
Rafał Dzięgiel
749796a12f clapper: doc: Update enhancer proxy docs 2025-05-23 16:11:46 +02:00
Rafał Dzięgiel
c557c11e86 clapper: Check enhancer config existence before applying it
Fixes crash due to trying to apply config for an enhancer while
there are no settings in this enhancer to be applied
2025-05-23 08:12:09 +02:00
Rafał Dzięgiel
a2f67a9bc0 Merge pull request #553 from Rafostar/harvest-caching
clapper: Implement harvest caching
2025-05-21 19:49:26 +02:00
Rafał Dzięgiel
ddc0a4d8f9 clapper: doc: Fix missing transfer annotation 2025-05-21 19:42:27 +02:00
Rafał Dzięgiel
92e3e686db clapper: doc: Fix adaptive-start-bitrate description 2025-05-21 18:36:19 +02:00
35 changed files with 1720 additions and 161 deletions

View File

@@ -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);

View File

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

View File

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

View File

@@ -20,6 +20,7 @@
#pragma once
#include <glib.h>
#include <glib-object.h>
#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

View File

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

View File

@@ -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);

View File

@@ -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"
@@ -213,7 +216,7 @@ clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_
copy->pspecs = g_new (GParamSpec *, copy->n_pspecs);
for (i = 0; i < src_proxy->n_pspecs; ++i)
copy->pspecs[i] = src_proxy->pspecs[i];
copy->pspecs[i] = g_param_spec_ref (src_proxy->pspecs[i]);
copy->scope = CLAPPER_ENHANCER_PARAM_LOCAL;
@@ -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);
}
}
@@ -706,7 +691,7 @@ clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *self)
*
* Get extra data from enhancer plugin info file specified by @key.
*
* External data in the plugin info file is prefixed with `X-`.
* Extra data in the plugin info file is prefixed with `X-`.
* For example `X-Schemes=https`.
*
* Returns: (nullable): extra data value of the proxied enhancer.
@@ -740,6 +725,10 @@ clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *self, const gchar *
* calling this function with "X-Schemes" as key and "http" as value will
* return %TRUE.
*
* It is also safe to call this function when there is no such @key
* in plugin info file. Use [method@Clapper.EnhancerProxy.get_extra_data]
* if you need to know whether key exists.
*
* Returns: whether list named with @key existed and contained @value.
*
* Since: 0.10
@@ -943,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
@@ -1013,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);
}
/**
@@ -1080,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

View File

@@ -33,6 +33,9 @@ static HMODULE _enhancers_dll_handle = NULL;
// Supported interfaces
#include "clapper-extractable.h"
#include "clapper-reactable.h"
#include <clapper-functionalities-availability.h>
#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 */

View File

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

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

@@ -23,7 +23,7 @@
#include <gst/pbutils/pbutils.h>
#include "clapper-media-item.h"
#include "clapper-player-private.h"
#include "clapper-player.h"
#include "clapper-app-bus-private.h"
G_BEGIN_DECLS

View File

@@ -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"
@@ -43,6 +44,7 @@ struct _ClapperMediaItem
gchar *uri;
gchar *suburi;
GstTagList *tags;
ClapperTimeline *timeline;
guint id;
@@ -56,6 +58,13 @@ struct _ClapperMediaItem
gboolean used;
};
typedef struct
{
ClapperMediaItem *item;
gboolean changed;
gboolean from_user;
} ClapperMediaItemTagIterData;
enum
{
PROP_0,
@@ -63,6 +72,7 @@ enum
PROP_URI,
PROP_SUBURI,
PROP_CACHE_LOCATION,
PROP_TAGS,
PROP_TITLE,
PROP_CONTAINER_FORMAT,
PROP_DURATION,
@@ -111,8 +121,8 @@ clapper_media_item_new (const gchar *uri)
/* FIXME: Set initial container format from file extension parsing */
GST_TRACE_OBJECT (item, "New media item, ID: %u, URI: %s, title: %s",
item->id, item->uri, item->title);
GST_TRACE_OBJECT (item, "New media item, ID: %u, URI: \"%s\", title: \"%s\"",
item->id, item->uri, GST_STR_NULL (item->title));
return item;
}
@@ -258,27 +268,6 @@ clapper_media_item_get_suburi (ClapperMediaItem *self)
return suburi;
}
static gboolean
clapper_media_item_take_title (ClapperMediaItem *self, gchar *title,
ClapperAppBus *app_bus)
{
gboolean changed;
GST_OBJECT_LOCK (self);
if ((changed = g_strcmp0 (self->title, title) != 0)) {
g_free (self->title);
self->title = title;
}
GST_OBJECT_UNLOCK (self);
if (changed)
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]);
else
g_free (title);
return changed;
}
/**
* clapper_media_item_get_title:
* @item: a #ClapperMediaItem
@@ -305,25 +294,24 @@ clapper_media_item_get_title (ClapperMediaItem *self)
return title;
}
static gboolean
clapper_media_item_take_container_format (ClapperMediaItem *self, gchar *container_format,
ClapperAppBus *app_bus)
static inline gboolean
_refresh_tag_prop_unlocked (ClapperMediaItem *self, const gchar *tag,
gboolean from_user, gchar **tag_ptr)
{
gboolean changed;
const gchar *string;
GST_OBJECT_LOCK (self);
if ((changed = g_strcmp0 (self->container_format, container_format) != 0)) {
g_free (self->container_format);
self->container_format = container_format;
}
GST_OBJECT_UNLOCK (self);
if ((*tag_ptr && from_user) // if already set, user cannot modify it
|| !gst_tag_list_peek_string_index (self->tags, tag, 0, &string) // guarantees non-empty string
|| (g_strcmp0 (*tag_ptr, string) == 0))
return FALSE;
if (changed)
clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CONTAINER_FORMAT]);
else
g_free (container_format);
GST_LOG_OBJECT (self, "Tag prop \"%s\" update: \"%s\" -> \"%s\"",
tag, GST_STR_NULL (*tag_ptr), string);
return changed;
g_free (*tag_ptr);
*tag_ptr = g_strdup (string);
return TRUE;
}
/**
@@ -333,6 +321,8 @@ clapper_media_item_take_container_format (ClapperMediaItem *self, gchar *contain
* Get media item container format.
*
* Returns: (transfer full) (nullable): media container format.
*
* Deprecated: 0.10: Get `container-format` from [property@Clapper.MediaItem:tags] instead.
*/
gchar *
clapper_media_item_get_container_format (ClapperMediaItem *self)
@@ -389,11 +379,214 @@ clapper_media_item_get_duration (ClapperMediaItem *self)
return duration;
}
/**
* clapper_media_item_get_tags:
* @item: a #ClapperMediaItem
*
* Get readable list of tags stored in media item.
*
* Returns: (transfer full): a #GstTagList.
*
* Since: 0.10
*/
GstTagList *
clapper_media_item_get_tags (ClapperMediaItem *self)
{
GstTagList *tags = NULL;
g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL);
GST_OBJECT_LOCK (self);
tags = gst_tag_list_ref (self->tags);
GST_OBJECT_UNLOCK (self);
return tags;
}
static void
_tags_replace_func (const GstTagList *tags, const gchar *tag, ClapperMediaItemTagIterData *data)
{
ClapperMediaItem *self = data->item;
guint index = 0;
gboolean replace = FALSE;
while (TRUE) {
const GValue *old_value = gst_tag_list_get_value_index (self->tags, tag, index);
const GValue *new_value = gst_tag_list_get_value_index (tags, tag, index);
/* Number of old values is the same or greater and
* all values until this iteration were the same */
if (!new_value)
break;
/* A wild new tag appeared */
if (!old_value) {
replace = TRUE;
break;
}
/* Users can only set non-existing tags */
if (data->from_user)
break;
/* Check with tolerance for doubles */
if (G_VALUE_TYPE (old_value) == G_TYPE_DOUBLE
&& G_VALUE_TYPE (new_value) == G_TYPE_DOUBLE) {
gdouble old_dbl, new_dbl;
old_dbl = g_value_get_double (old_value);
new_dbl = g_value_get_double (new_value);
if ((replace = !G_APPROX_VALUE (old_dbl, new_dbl, FLT_EPSILON)))
break;
} else if (gst_value_compare (old_value, new_value) != GST_VALUE_EQUAL) {
replace = TRUE;
break;
}
++index;
}
if (replace) {
const GValue *value;
index = 0;
GST_LOG_OBJECT (self, "Replacing \"%s\" tag value", tag);
/* Ensure writable, but only when replacing something */
if (!data->changed) {
self->tags = gst_tag_list_make_writable (self->tags);
data->changed = TRUE;
}
/* Replace first tag value (so it becomes sole member) */
value = gst_tag_list_get_value_index (tags, tag, index);
gst_tag_list_add_value (self->tags, GST_TAG_MERGE_REPLACE, tag, value);
/* Append any remaining tags (so next time we iterate indexes will match) */
while ((value = gst_tag_list_get_value_index (tags, tag, ++index)))
gst_tag_list_add_value (self->tags, GST_TAG_MERGE_APPEND, tag, value);
}
}
static gboolean
clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagList *tags,
ClapperAppBus *app_bus, gboolean from_user, ClapperReactableItemUpdatedFlags *flags)
{
ClapperMediaItemTagIterData data;
gboolean title_changed = FALSE, cont_changed = FALSE;
GST_OBJECT_LOCK (self);
data.item = self;
data.changed = FALSE;
data.from_user = from_user;
if (G_LIKELY (tags != self->tags))
gst_tag_list_foreach (tags, (GstTagForeachFunc) _tags_replace_func, &data);
if (data.changed) {
*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);
}
GST_OBJECT_UNLOCK (self);
if (!data.changed)
return FALSE;
if (app_bus) {
GstObject *src = GST_OBJECT_CAST (self);
clapper_app_bus_post_prop_notify (app_bus, src, param_specs[PROP_TAGS]);
if (title_changed)
clapper_app_bus_post_prop_notify (app_bus, src, param_specs[PROP_TITLE]);
if (cont_changed)
clapper_app_bus_post_prop_notify (app_bus, src, param_specs[PROP_CONTAINER_FORMAT]);
} else {
GObject *src = G_OBJECT (self);
clapper_utils_prop_notify_on_main_sync (src, param_specs[PROP_TAGS]);
if (title_changed)
clapper_utils_prop_notify_on_main_sync (src, param_specs[PROP_TITLE]);
if (cont_changed)
clapper_utils_prop_notify_on_main_sync (src, param_specs[PROP_CONTAINER_FORMAT]);
}
return TRUE;
}
/**
* clapper_media_item_populate_tags:
* @item: a #ClapperMediaItem
* @tags: a #GstTagList of GLOBAL scope
*
* Populate non-existing tags in @item tag list.
*
* Passed @tags must use [enum@Gst.TagScope.GLOBAL] scope.
*
* Note that tags are automatically determined during media playback
* and those take precedence. This function can be useful if an app can
* determine some tags that are not in media metadata or for filling
* item with some initial/cached tags to display in UI before playback.
*
* When a tag already exists in the tag list (was populated) this
* function will not overwrite it. If you really need to permanently
* override some tags in media, you can use `taginject` element as
* player video/audio filter instead.
*
* Returns: whether at least one tag got updated.
*
* Since: 0.10
*/
gboolean
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);
g_return_val_if_fail (tags != NULL, FALSE);
if (G_UNLIKELY (gst_tag_list_get_scope (tags) != GST_TAG_SCOPE_GLOBAL)) {
g_warning ("Cannot populate media item tags using a list with non-global tag scope");
return FALSE;
}
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, &flags);
if (changed && player) {
ClapperFeaturesManager *features_manager;
if (player->reactables_manager)
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);
}
gst_clear_object (&player);
return changed;
}
/**
* clapper_media_item_get_timeline:
* @item: a #ClapperMediaItem
*
* Get the [class@Clapper.Timeline] assosiated with @item.
* Get the [class@Clapper.Timeline] associated with @item.
*
* Returns: (transfer none): a #ClapperTimeline of item.
*/
@@ -405,21 +598,6 @@ clapper_media_item_get_timeline (ClapperMediaItem *self)
return self->timeline;
}
static gboolean
clapper_media_item_update_from_container_tags (ClapperMediaItem *self, const GstTagList *tags,
ClapperAppBus *app_bus)
{
gchar *string = NULL;
gboolean changed = FALSE;
if (gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &string))
changed |= clapper_media_item_take_container_format (self, string, app_bus);
if (gst_tag_list_get_string (tags, GST_TAG_TITLE, &string))
changed |= clapper_media_item_take_title (self, string, app_bus);
return changed;
}
void
clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagList *tags,
ClapperPlayer *player)
@@ -427,11 +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_update_from_container_tags (self, tags, player->app_bus);
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, flags);
if ((features_manager = clapper_player_get_features_manager (player)))
clapper_features_manager_trigger_item_updated (features_manager, self);
}
@@ -444,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;
@@ -459,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_update_from_container_tags (self, tags, player->app_bus);
changed |= clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags);
}
gst_discoverer_stream_info_unref (sinfo);
}
@@ -470,11 +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, flags);
if ((features_manager = clapper_player_get_features_manager (player)))
clapper_features_manager_trigger_item_updated (features_manager, self);
}
@@ -542,6 +729,9 @@ clapper_media_item_get_used (ClapperMediaItem *self)
static void
clapper_media_item_init (ClapperMediaItem *self)
{
self->tags = gst_tag_list_new_empty ();
gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL);
self->timeline = clapper_timeline_new ();
gst_object_set_parent (GST_OBJECT_CAST (self->timeline), GST_OBJECT_CAST (self));
}
@@ -571,6 +761,8 @@ clapper_media_item_finalize (GObject *object)
g_free (self->title);
g_free (self->container_format);
gst_tag_list_unref (self->tags);
gst_object_unparent (GST_OBJECT_CAST (self->timeline));
gst_object_unref (self->timeline);
@@ -617,6 +809,9 @@ clapper_media_item_get_property (GObject *object, guint prop_id,
case PROP_SUBURI:
g_value_take_string (value, clapper_media_item_get_suburi (self));
break;
case PROP_TAGS:
g_value_take_boxed (value, clapper_media_item_get_tags (self));
break;
case PROP_TITLE:
g_value_take_string (value, clapper_media_item_get_title (self));
break;
@@ -686,10 +881,27 @@ clapper_media_item_class_init (ClapperMediaItemClass *klass)
NULL, NULL, NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperMediaItem:tags:
*
* A readable list of tags stored in media item.
*
* Since: 0.10
*/
param_specs[PROP_TAGS] = g_param_spec_boxed ("tags",
NULL, NULL, GST_TYPE_TAG_LIST,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/* FIXME: 1.0: Consider rename to e.g. "(menu/display)-title"
* and also make it non-nullable (return URI as final fallback) */
/**
* ClapperMediaItem:title:
*
* Media title.
*
* This might be a different string compared to `title` from
* [property@Clapper.MediaItem:tags], as this gives parsed
* title from file name/URI as fallback when no `title` tag.
*/
param_specs[PROP_TITLE] = g_param_spec_string ("title",
NULL, NULL, NULL,
@@ -699,15 +911,21 @@ clapper_media_item_class_init (ClapperMediaItemClass *klass)
* ClapperMediaItem:container-format:
*
* Media container format.
*
* Deprecated: 0.10: Get `container-format` from [property@Clapper.MediaItem:tags] instead.
*/
param_specs[PROP_CONTAINER_FORMAT] = g_param_spec_string ("container-format",
NULL, NULL, NULL,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED);
/**
* ClapperMediaItem:duration:
*
* Media duration as a decimal number in seconds.
*
* This might be a different value compared to `duration` from
* [property@Clapper.MediaItem:tags], as this value is updated
* during decoding instead of being a fixed value from metadata.
*/
param_specs[PROP_DURATION] = g_param_spec_double ("duration",
NULL, NULL, 0, G_MAXDOUBLE, 0,

View File

@@ -27,6 +27,7 @@
#include <glib-object.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include <gst/tag/tag.h>
#include <clapper/clapper-visibility.h>
#include <clapper/clapper-timeline.h>
@@ -63,12 +64,18 @@ gchar * clapper_media_item_get_suburi (ClapperMediaItem *item);
CLAPPER_API
gchar * clapper_media_item_get_title (ClapperMediaItem *item);
CLAPPER_API
CLAPPER_DEPRECATED_FOR(clapper_media_item_get_tags)
gchar * clapper_media_item_get_container_format (ClapperMediaItem *item);
CLAPPER_API
gdouble clapper_media_item_get_duration (ClapperMediaItem *item);
CLAPPER_API
GstTagList * clapper_media_item_get_tags (ClapperMediaItem *item);
CLAPPER_API
gboolean clapper_media_item_populate_tags (ClapperMediaItem *item, const GstTagList *tags);
CLAPPER_API
ClapperTimeline * clapper_media_item_get_timeline (ClapperMediaItem *item);

View File

@@ -173,6 +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,
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);
}
@@ -1046,6 +1050,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);
}

View File

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

View File

@@ -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);
@@ -2833,7 +2856,7 @@ clapper_player_class_init (ClapperPlayerClass *klass)
* An initial bitrate (bits/s) to select during
* starting adaptive streaming such as DASH or HLS.
*
* If value is higher than lowest available bitrate in streaming
* If value is lower than the lowest available bitrate in streaming
* manifest, then lowest possible bitrate will be selected.
*
* Since: 0.8

View File

@@ -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);

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,229 @@
/* 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
* @flags: flags informing which properties were 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, ClapperReactableItemUpdatedFlags flags);
/**
* 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

@@ -0,0 +1,82 @@
/* 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
#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, ClapperReactableItemUpdatedFlags flags);
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

View File

@@ -0,0 +1,534 @@
/* 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.
*/
#include <gst/gst.h>
#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)),
g_value_get_flags (extra_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, ClapperReactableItemUpdatedFlags _flags)
{
_BUS_POST_EVENT_DUAL (_EVENT (ITEM_UPDATED), object, CLAPPER_TYPE_MEDIA_ITEM, item, flags, CLAPPER_TYPE_REACTABLE_ITEM_UPDATED_FLAGS, _flags);
}
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;
}

View File

@@ -52,7 +52,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (ClapperThreadedObject, clapper_threaded_object, GST_
* Useful when you want to invoke object thread to do some
* action in it from a different thread.
*
* Returns: a #GMainContext of the object used thread.
* Returns: (transfer none): a #GMainContext of the object used thread.
*/
GMainContext *
clapper_threaded_object_get_context (ClapperThreadedObject *self)

View File

@@ -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,19 @@ 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))))) {
clapper_features_manager_trigger_item_updated (features_manager, item);
gst_object_unref (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);
gst_object_unref (item);
}
gst_object_unref (player);

View File

@@ -45,10 +45,16 @@ void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaI
G_GNUC_INTERNAL
void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue);
G_GNUC_INTERNAL
void clapper_utils_prop_notify_on_main_sync (GObject *object, GParamSpec *pspec);
G_GNUC_INTERNAL
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

View File

@@ -39,6 +39,12 @@ typedef struct
ClapperUtilsQueueAlterMethod method;
} ClapperUtilsQueueAlterData;
typedef struct
{
GObject *object;
GParamSpec *pspec;
} ClapperUtilsPropNotifyData;
void
clapper_utils_initialize (void)
{
@@ -71,6 +77,27 @@ clapper_utils_queue_alter_data_free (ClapperUtilsQueueAlterData *data)
g_free (data);
}
static ClapperUtilsPropNotifyData *
clapper_utils_prop_notify_data_new (GObject *object, GParamSpec *pspec)
{
ClapperUtilsPropNotifyData *data = g_new (ClapperUtilsPropNotifyData, 1);
data->object = object;
data->pspec = pspec;
GST_TRACE ("Created prop notify data: %p", data);
return data;
}
static void
clapper_utils_prop_notify_data_free (ClapperUtilsPropNotifyData *data)
{
GST_TRACE ("Freeing prop notify data: %p", data);
g_free (data);
}
static gpointer
clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data)
{
@@ -110,6 +137,15 @@ clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data)
return NULL;
}
static gpointer
clapper_utils_prop_notify_on_main (ClapperUtilsPropNotifyData *data)
{
GST_DEBUG ("Prop notify invoked");
g_object_notify_by_pspec (data->object, data->pspec);
return NULL;
}
static inline void
clapper_utils_queue_alter_invoke_on_main_sync_take (ClapperUtilsQueueAlterData *data)
{
@@ -155,6 +191,27 @@ clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue)
clapper_utils_queue_alter_invoke_on_main_sync_take (data);
}
void
clapper_utils_prop_notify_on_main_sync (GObject *object, GParamSpec *pspec)
{
ClapperUtilsPropNotifyData *data;
if (g_main_context_is_owner (g_main_context_default ())) { // already in main thread
g_object_notify_by_pspec (object, pspec);
return;
}
data = clapper_utils_prop_notify_data_new (object, pspec);
GST_DEBUG ("Invoking prop notify on main...");
clapper_shared_utils_context_invoke_sync_full (g_main_context_default (),
(GThreadFunc) clapper_utils_prop_notify_on_main, data,
(GDestroyNotify) clapper_utils_prop_notify_data_free);
GST_DEBUG ("Prop notify invoke finished");
}
gchar *
clapper_utils_uri_from_file (GFile *file)
{
@@ -214,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;
}

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

@@ -95,7 +95,8 @@ clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data)
#endif
if (G_LIKELY (extractable != NULL)) {
clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) extractable);
if (config)
clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) extractable);
success = clapper_extractable_extract (extractable, data->uri,
harvest, data->cancellable, data->error);

View File

@@ -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);

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,8 @@ clapper_sources = [
'clapper-playbin-bus.c',
'clapper-player.c',
'clapper-queue.c',
'clapper-reactable.c',
'clapper-reactables-manager.c',
'clapper-stream.c',
'clapper-stream-list.c',
'clapper-subtitle-stream.c',
@@ -235,6 +238,7 @@ if build_gir
clapper_enums,
],
extra_args: [
gir_init_section,
'--quiet',
'--warn-all',
'-DCLAPPER_COMPILATION',

View File

@@ -1,6 +1,6 @@
// Skipped by GI, but Vala can handle it fine
//init_get_option_group skip=false
*_FORMAT skip=false
*.peek_* skip=false
// Init func compatibility
init.argv unowned

View File

@@ -594,11 +594,14 @@ static gboolean
gst_clapper_sink_start (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean with_clapper_gtk;
GST_INFO_OBJECT (self, "Start");
if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_start_on_main, self)))) {
with_clapper_gtk = g_type_from_name ("ClapperGtkVideo");
if (G_UNLIKELY (!with_clapper_gtk && !(! !gst_gtk_invoke_on_main (
(GThreadFunc) (GCallback) gst_clapper_sink_start_on_main, self)))) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("GtkWidget could not be created"), (NULL));

View File

@@ -4,6 +4,13 @@ build_gir = (gir.found() and not get_option('introspection').disabled())
vapigen = find_program('vapigen', required: get_option('vapi'))
build_vapi = (vapigen.found() and not get_option('vapi').disabled())
gir_init_section = '--add-init-section=extern void gst_init(gint*,gchar**);' + \
'g_setenv("GST_REGISTRY_DISABLE", "yes", TRUE);' + \
'g_setenv("GST_REGISTRY_1_0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
'gst_init(NULL,NULL);'
subdir('gst')
subdir('clapper')
subdir('clapper-gtk')

View File

@@ -41,5 +41,11 @@
#endif
#define @CLAPPER_API@_API _@CLAPPER_API@_VISIBILITY
#if !defined(@CLAPPER_API@_COMPILATION)
#define @CLAPPER_API@_DEPRECATED G_DEPRECATED _@CLAPPER_API@_VISIBILITY
#define @CLAPPER_API@_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _@CLAPPER_API@_VISIBILITY
#else
#define @CLAPPER_API@_DEPRECATED _@CLAPPER_API@_VISIBILITY
#define @CLAPPER_API@_DEPRECATED_FOR(f) _@CLAPPER_API@_VISIBILITY
#endif