timeline-element: add signals for child properties

Add the child-property-added and child-property-removed signals to
GESTimelineElement.

GESContainer is able to use this to keep their child properties in sync
with their children: if they are added or removed from the child, they
are also added or removed from the container.
This commit is contained in:
Henry Wilkes 2020-03-03 14:31:10 +00:00
parent 74ae0ba5df
commit c63fb5db0e
7 changed files with 718 additions and 45 deletions

View file

@ -61,9 +61,11 @@ typedef struct
GstClockTime inpoint_offset;
gint32 priority_offset;
guint start_notifyid;
guint duration_notifyid;
guint inpoint_notifyid;
gulong start_notifyid;
gulong duration_notifyid;
gulong inpoint_notifyid;
gulong child_property_added_notifyid;
gulong child_property_removed_notifyid;
} ChildMapping;
enum
@ -120,6 +122,11 @@ _free_mapping (ChildMapping * mapping)
g_signal_handler_disconnect (child, mapping->duration_notifyid);
if (mapping->inpoint_notifyid)
g_signal_handler_disconnect (child, mapping->inpoint_notifyid);
if (mapping->child_property_added_notifyid)
g_signal_handler_disconnect (child, mapping->child_property_added_notifyid);
if (mapping->child_property_removed_notifyid)
g_signal_handler_disconnect (child,
mapping->child_property_removed_notifyid);
ges_timeline_element_set_parent (child, NULL);
g_slice_free (ChildMapping, mapping);
@ -212,60 +219,104 @@ _set_duration (GESTimelineElement * element, GstClockTime duration)
return TRUE;
}
static void
_add_childs_child_property (GESTimelineElement * container_child,
GObject * prop_child, GParamSpec * property, GESContainer * container)
{
/* the container_child is kept as the owner of this child property when
* we register it on ourselves, but we use the same GObject child
* instance who the property comes from */
gboolean res =
ges_timeline_element_add_child_property_full (GES_TIMELINE_ELEMENT
(container), container_child, property, prop_child);
if (!res)
GST_INFO_OBJECT (container, "Could not register the child property '%s' "
"of our child %" GES_FORMAT " for the object %" GST_PTR_FORMAT,
property->name, GES_ARGS (container_child), prop_child);
}
static void
_ges_container_add_child_properties (GESContainer * container,
GESTimelineElement * child)
{
guint n_props, i;
/* use get_children_properties, rather than list_children_properties
* to ensure we are getting all the properties, without any interference
* from the ->list_children_properties vmethods */
GParamSpec **child_props =
ges_timeline_element_list_children_properties (child,
ges_timeline_element_get_children_properties (child,
&n_props);
for (i = 0; i < n_props; i++) {
GObject *prop_child;
gchar *prop_name = g_strdup_printf ("%s::%s",
g_type_name (child_props[i]->owner_type),
child_props[i]->name);
if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
ges_timeline_element_add_child_property_full (GES_TIMELINE_ELEMENT
(container), child, child_props[i], prop_child);
gst_object_unref (prop_child);
}
g_free (prop_name);
g_param_spec_unref (child_props[i]);
GParamSpec *property = child_props[i];
GObject *prop_child =
ges_timeline_element_get_child_from_child_property (child, property);
if (prop_child)
_add_childs_child_property (child, prop_child, property, container);
g_param_spec_unref (property);
}
g_free (child_props);
}
static void
_remove_childs_child_property (GESTimelineElement * container_child,
GObject * prop_child, GParamSpec * property, GESContainer * container)
{
/* NOTE: some children may share the same GParamSpec. Currently, only
* the first such child added will have its children properties
* successfully registered for the container (even though the GObject
* child who the properties belong to will be a different instance). As
* such, we only want to remove the child property if it corresponds to
* the same instance that the parent container has.
* E.g. if we add child1 and child2, that have the same (or some
* overlapping) children properties. And child1 is added before child2,
* then child2's overlapping children properties would not be registered.
* If we remove child2, we do *not* want to removed the child properties
* for child1 because they belong to a GObject instance that we still
* have in our control.
* If we remove child1, we *do* want to remove the child properties for
* child1, even though child2 may overlap with some of them, because we
* are loosing the specific GObject instance that it belongs to!
* We could try and register the ones that match for the other children.
* However, it is probably simpler to change
* ges_timeline_element_add_child_property_full to accept the same
* GParamSpec, for different instances.
*/
GESTimelineElement *element = GES_TIMELINE_ELEMENT (container);
GObject *our_prop_child =
ges_timeline_element_get_child_from_child_property (element, property);
if (our_prop_child == prop_child)
ges_timeline_element_remove_child_property (element, property);
else
GST_INFO_OBJECT (container, "Not removing child property '%s' for child"
" %" GES_FORMAT " because it derives from the object %" GST_PTR_FORMAT
"(%p) rather than the object %" GST_PTR_FORMAT "(%p)", property->name,
GES_ARGS (container_child), prop_child, prop_child, our_prop_child,
our_prop_child);
}
static void
_ges_container_remove_child_properties (GESContainer * container,
GESTimelineElement * child)
{
guint n_props, i;
/* use get_children_properties, rather than list_children_properties
* to ensure we are getting all the properties, without any interference
* from the ->list_children_properties vmethods */
GParamSpec **child_props =
ges_timeline_element_list_children_properties (child,
ges_timeline_element_get_children_properties (child,
&n_props);
for (i = 0; i < n_props; i++) {
GObject *prop_child;
gchar *prop_name = g_strdup_printf ("%s::%s",
g_type_name (child_props[i]->owner_type),
child_props[i]->name);
if (ges_timeline_element_lookup_child (child, prop_name, &prop_child, NULL)) {
ges_timeline_element_remove_child_property (GES_TIMELINE_ELEMENT
(container), child_props[i]);
gst_object_unref (prop_child);
}
g_free (prop_name);
g_param_spec_unref (child_props[i]);
GParamSpec *property = child_props[i];
GObject *prop_child =
ges_timeline_element_get_child_from_child_property (child, property);
if (prop_child)
_remove_childs_child_property (child, prop_child, property, container);
g_param_spec_unref (property);
}
g_free (child_props);
@ -819,6 +870,12 @@ ges_container_add (GESContainer * container, GESTimelineElement * child)
}
_ges_container_add_child_properties (container, child);
mapping->child_property_added_notifyid =
g_signal_connect (G_OBJECT (child), "child-property-added",
G_CALLBACK (_add_childs_child_property), container);
mapping->child_property_removed_notifyid =
g_signal_connect (G_OBJECT (child), "child-property-removed",
G_CALLBACK (_remove_childs_child_property), container);
priv->adding_children = g_list_prepend (priv->adding_children, child);
g_signal_emit (container, ges_container_signals[CHILD_ADDED_SIGNAL], 0,
@ -892,7 +949,7 @@ ges_container_remove (GESContainer * container, GESTimelineElement * child)
}
container->children = g_list_remove (container->children, child);
/* Let it live removing from our mappings */
/* Let it live removing from our mappings, also disconnects signals */
g_hash_table_remove (priv->mappings, child);
_ges_container_remove_child_properties (container, child);

View file

@ -445,10 +445,15 @@ G_GNUC_INTERNAL gdouble ges_timeline_element_get_media_duration_factor(GESTimeli
G_GNUC_INTERNAL GESTimelineElement * ges_timeline_element_get_copied_from (GESTimelineElement *self);
G_GNUC_INTERNAL GESTimelineElementFlags ges_timeline_element_flags (GESTimelineElement *self);
G_GNUC_INTERNAL void ges_timeline_element_set_flags (GESTimelineElement *self, GESTimelineElementFlags flags);
G_GNUC_INTERNAL gboolean ges_timeline_element_add_child_property_full (GESTimelineElement *self,
GESTimelineElement *owner,
GParamSpec *pspec,
GObject *child);
G_GNUC_INTERNAL gboolean ges_timeline_element_add_child_property_full (GESTimelineElement *self,
GESTimelineElement *owner,
GParamSpec *pspec,
GObject *child);
G_GNUC_INTERNAL GObject * ges_timeline_element_get_child_from_child_property (GESTimelineElement * self,
GParamSpec * pspec);
G_GNUC_INTERNAL GParamSpec ** ges_timeline_element_get_children_properties (GESTimelineElement * self,
guint * n_properties);
#define ELEMENT_FLAGS(obj) (ges_timeline_element_flags (GES_TIMELINE_ELEMENT(obj)))
#define ELEMENT_SET_FLAG(obj,flag) (ges_timeline_element_set_flags(GES_TIMELINE_ELEMENT(obj), (ELEMENT_FLAGS(obj) | (flag))))

View file

@ -120,6 +120,8 @@ enum
enum
{
DEEP_NOTIFY,
CHILD_PROPERTY_ADDED,
CHILD_PROPERTY_REMOVED,
LAST_SIGNAL
};
@ -219,8 +221,8 @@ _lookup_child (GESTimelineElement * self, const gchar * prop_name,
return res;
}
static GParamSpec **
default_list_children_properties (GESTimelineElement * self,
GParamSpec **
ges_timeline_element_get_children_properties (GESTimelineElement * self,
guint * n_properties)
{
GParamSpec **pspec, *spec;
@ -495,10 +497,10 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
/**
* GESTimelineElement::deep-notify:
* @timeline_element: A #GESTtimelineElement
* @prop_object: The object that originated the signal
* @prop: The specification for the property that changed
* @prop_object: The child whose property has been set
* @prop: The specification for the property that been set
*
* Emitted when a child of @timeline_element has one of its registered
* Emitted when a child of the element has one of its registered
* properties set. See ges_timeline_element_add_child_property().
* Note that unlike #GObject::notify, a child property name can not be
* used as a signal detail.
@ -509,6 +511,39 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_PARAM);
/**
* GESTimelineElement::child-property-added:
* @timeline_element: A #GESTtimelineElement
* @prop_object: The child whose property has been registered
* @prop: The specification for the property that has been registered
*
* Emitted when the element has a new child property registered. See
* ges_timeline_element_add_child_property().
*
* Note that some GES elements will be automatically created with
* pre-registered children properties. You can use
* ges_timeline_element_list_children_properties() to list these.
*/
ges_timeline_element_signals[CHILD_PROPERTY_ADDED] =
g_signal_new ("child-property-added", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
G_TYPE_OBJECT, G_TYPE_PARAM);
/**
* GESTimelineElement::child-property-removed:
* @timeline_element: A #GESTtimelineElement
* @prop_object: The child whose property has been unregistered
* @prop: The specification for the property that has been unregistered
*
* Emitted when the element has a child property unregistered. See
* ges_timeline_element_remove_child_property().
*/
ges_timeline_element_signals[CHILD_PROPERTY_REMOVED] =
g_signal_new ("child-property-removed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
G_TYPE_OBJECT, G_TYPE_PARAM);
object_class->dispose = ges_timeline_element_dispose;
object_class->finalize = ges_timeline_element_finalize;
@ -525,7 +560,8 @@ ges_timeline_element_class_init (GESTimelineElementClass * klass)
klass->roll_end = NULL;
klass->trim = NULL;
klass->list_children_properties = default_list_children_properties;
klass->list_children_properties =
ges_timeline_element_get_children_properties;
klass->lookup_child = _lookup_child;
klass->set_child_property = _set_child_property;
}
@ -751,6 +787,13 @@ ges_timeline_element_add_child_property_full (GESTimelineElement * self,
gchar *signame;
ChildPropHandler *handler;
/* FIXME: allow the same pspec, provided the child is different. This
* is important for containers that may have duplicate children
* If this is changed, _remove_childs_child_property in ges-container.c
* should be changed to reflect this.
* We could hack around this by copying the pspec into a new instance
* of GParamSpec, but there is no such GLib method, and it would break
* the usage of get_..._from_pspec and set_..._from_pspec */
if (g_hash_table_contains (self->priv->children_props, pspec)) {
GST_INFO_OBJECT (self, "Child property already exists: %s", pspec->name);
return FALSE;
@ -769,10 +812,25 @@ ges_timeline_element_add_child_property_full (GESTimelineElement * self,
g_hash_table_insert (self->priv->children_props, g_param_spec_ref (pspec),
handler);
g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_ADDED], 0,
child, pspec);
g_free (signame);
return TRUE;
}
GObject *
ges_timeline_element_get_child_from_child_property (GESTimelineElement * self,
GParamSpec * pspec)
{
ChildPropHandler *handler =
g_hash_table_lookup (self->priv->children_props, pspec);
if (handler)
return handler->child;
return NULL;
}
/*********************************************
* API implementation *
*********************************************/
@ -1748,6 +1806,10 @@ gboolean
ges_timeline_element_add_child_property (GESTimelineElement * self,
GParamSpec * pspec, GObject * child)
{
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
g_return_val_if_fail (G_IS_OBJECT (child), FALSE);
return ges_timeline_element_add_child_property_full (self, NULL, pspec,
child);
}
@ -1769,6 +1831,7 @@ ges_timeline_element_get_child_property_by_pspec (GESTimelineElement * self,
ChildPropHandler *handler;
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
g_return_if_fail (G_IS_PARAM_SPEC (pspec));
handler = g_hash_table_lookup (self->priv->children_props, pspec);
if (!handler)
@ -1799,7 +1862,8 @@ void
ges_timeline_element_set_child_property_by_pspec (GESTimelineElement * self,
GParamSpec * pspec, const GValue * value)
{
g_return_if_fail (GES_IS_TRACK_ELEMENT (self));
g_return_if_fail (GES_IS_TIMELINE_ELEMENT (self));
g_return_if_fail (G_IS_PARAM_SPEC (pspec));
set_child_property_by_pspec (self, pspec, value);
}
@ -2179,7 +2243,29 @@ gboolean
ges_timeline_element_remove_child_property (GESTimelineElement * self,
GParamSpec * pspec)
{
return g_hash_table_remove (self->priv->children_props, pspec);
gpointer key, value;
GParamSpec *found_pspec;
ChildPropHandler *handler;
g_return_val_if_fail (GES_IS_TIMELINE_ELEMENT (self), FALSE);
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
if (!g_hash_table_steal_extended (self->priv->children_props, pspec,
&key, &value)) {
GST_WARNING_OBJECT (self, "No child property with pspec %p (%s) found",
pspec, pspec->name);
return FALSE;
}
found_pspec = G_PARAM_SPEC (key);
handler = (ChildPropHandler *) value;
g_signal_emit (self, ges_timeline_element_signals[CHILD_PROPERTY_REMOVED], 0,
handler->child, found_pspec);
g_param_spec_unref (found_pspec);
_child_prop_handler_free (handler);
return TRUE;
}
/**

View file

@ -926,6 +926,311 @@ GST_START_TEST (test_can_add_effect)
GST_END_TEST;
GST_START_TEST (test_children_properties_contain)
{
GESTimeline *timeline;
GESLayer *layer;
GESClip *clip;
GList *tmp;
GParamSpec **clips_child_props, **childrens_child_props = NULL;
guint num_clips_props, num_childrens_props = 0;
ges_init ();
timeline = ges_timeline_new_audio_video ();
layer = ges_timeline_append_layer (timeline);
clip = GES_CLIP (ges_test_clip_new ());
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
fail_unless (ges_layer_add_clip (layer, clip));
clips_child_props =
ges_timeline_element_list_children_properties (GES_TIMELINE_ELEMENT
(clip), &num_clips_props);
fail_unless (clips_child_props);
fail_unless (num_clips_props);
fail_unless (GES_CONTAINER_CHILDREN (clip));
for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
childrens_child_props =
append_children_properties (childrens_child_props, tmp->data,
&num_childrens_props);
assert_property_list_match (clips_child_props, num_clips_props,
childrens_child_props, num_childrens_props);
free_children_properties (clips_child_props, num_clips_props);
free_children_properties (childrens_child_props, num_childrens_props);
gst_object_unref (timeline);
ges_deinit ();
}
GST_END_TEST;
static gboolean
_has_child_property (GESTimelineElement * element, GParamSpec * property)
{
gboolean has_prop = FALSE;
guint num_props, i;
GParamSpec **props =
ges_timeline_element_list_children_properties (element, &num_props);
for (i = 0; i < num_props; i++) {
if (props[i] == property)
has_prop = TRUE;
g_param_spec_unref (props[i]);
}
g_free (props);
return has_prop;
}
typedef struct
{
GstElement *child;
GParamSpec *property;
guint num_calls;
} PropChangedData;
#define _INIT_PROP_CHANGED_DATA(data) \
data.child = NULL; \
data.property = NULL; \
data.num_calls = 0;
static void
_prop_changed_cb (GESTimelineElement * element, GstElement * child,
GParamSpec * property, PropChangedData * data)
{
data->num_calls++;
data->property = property;
data->child = child;
}
#define _assert_prop_changed_data(element, data, num_cmp, chld_cmp, prop_cmp) \
fail_unless (num_cmp == data.num_calls, \
"%s: num calls to callback (%u) not the expected %u", element->name, \
data.num_calls, num_cmp); \
fail_unless (prop_cmp == data.property, \
"%s: property %s is not the expected property %s", element->name, \
data.property->name, prop_cmp ? ((GParamSpec *)prop_cmp)->name : NULL); \
fail_unless (chld_cmp == data.child, \
"%s: child %s is not the expected child %s", element->name, \
GST_ELEMENT_NAME (data.child), \
chld_cmp ? GST_ELEMENT_NAME (chld_cmp) : NULL);
#define _assert_int_val_child_prop(element, val, int_cmp, prop, prop_name) \
g_value_init (&val, G_TYPE_INT); \
ges_timeline_element_get_child_property_by_pspec (element, prop, &val); \
assert_equals_int (g_value_get_int (&val), int_cmp); \
g_value_unset (&val); \
g_value_init (&val, G_TYPE_INT); \
fail_unless (ges_timeline_element_get_child_property ( \
element, prop_name, &val)); \
assert_equals_int (g_value_get_int (&val), int_cmp); \
g_value_unset (&val); \
GST_START_TEST (test_children_properties_change)
{
GESTimeline *timeline;
GESLayer *layer;
GESTimelineElement *clip, *child;
PropChangedData clip_add_data, clip_remove_data, clip_notify_data,
child_add_data, child_remove_data, child_notify_data;
GstElement *sub_child;
GParamSpec *prop1, *prop2, *prop3;
GValue val = G_VALUE_INIT;
gint num_buffs;
ges_init ();
timeline = ges_timeline_new_audio_video ();
layer = ges_timeline_append_layer (timeline);
clip = GES_TIMELINE_ELEMENT (ges_test_clip_new ());
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip), 50);
fail_unless (ges_layer_add_clip (layer, GES_CLIP (clip)));
fail_unless (GES_CONTAINER_CHILDREN (clip));
child = GES_CONTAINER_CHILDREN (clip)->data;
/* fake sub-child */
sub_child = gst_element_factory_make ("fakesink", "sub-child");
fail_unless (sub_child);
gst_object_ref_sink (sub_child);
prop1 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child),
"num-buffers");
fail_unless (prop1);
prop2 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child), "dump");
fail_unless (prop2);
prop3 = g_object_class_find_property (G_OBJECT_GET_CLASS (sub_child),
"silent");
fail_unless (prop2);
_INIT_PROP_CHANGED_DATA (clip_add_data);
_INIT_PROP_CHANGED_DATA (clip_remove_data);
_INIT_PROP_CHANGED_DATA (clip_notify_data);
_INIT_PROP_CHANGED_DATA (child_add_data);
_INIT_PROP_CHANGED_DATA (child_remove_data);
_INIT_PROP_CHANGED_DATA (child_notify_data);
g_signal_connect (clip, "child-property-added",
G_CALLBACK (_prop_changed_cb), &clip_add_data);
g_signal_connect (clip, "child-property-removed",
G_CALLBACK (_prop_changed_cb), &clip_remove_data);
g_signal_connect (clip, "deep-notify",
G_CALLBACK (_prop_changed_cb), &clip_notify_data);
g_signal_connect (child, "child-property-added",
G_CALLBACK (_prop_changed_cb), &child_add_data);
g_signal_connect (child, "child-property-removed",
G_CALLBACK (_prop_changed_cb), &child_remove_data);
g_signal_connect (child, "deep-notify",
G_CALLBACK (_prop_changed_cb), &child_notify_data);
/* adding to child should also add it to the parent clip */
fail_unless (ges_timeline_element_add_child_property (child, prop1,
G_OBJECT (sub_child)));
fail_unless (_has_child_property (child, prop1));
fail_unless (_has_child_property (clip, prop1));
_assert_prop_changed_data (clip, clip_add_data, 1, sub_child, prop1);
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_add_data, 1, sub_child, prop1);
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
fail_unless (ges_timeline_element_add_child_property (child, prop2,
G_OBJECT (sub_child)));
fail_unless (_has_child_property (child, prop2));
fail_unless (_has_child_property (clip, prop2));
_assert_prop_changed_data (clip, clip_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
/* adding to parent does not add to the child */
fail_unless (ges_timeline_element_add_child_property (clip, prop3,
G_OBJECT (sub_child)));
fail_if (_has_child_property (child, prop3));
fail_unless (_has_child_property (clip, prop3));
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (clip, clip_notify_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_notify_data, 0, NULL, NULL);
/* both should be notified of a change in the value */
g_object_set (G_OBJECT (sub_child), "num-buffers", 100, NULL);
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (clip, clip_notify_data, 1, sub_child, prop1);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_notify_data, 1, sub_child, prop1);
_assert_int_val_child_prop (clip, val, 100, prop1,
"GstFakeSink::num-buffers");
_assert_int_val_child_prop (child, val, 100, prop1,
"GstFakeSink::num-buffers");
g_value_init (&val, G_TYPE_INT);
g_value_set_int (&val, 79);
ges_timeline_element_set_child_property_by_pspec (clip, prop1, &val);
g_value_unset (&val);
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (clip, clip_notify_data, 2, sub_child, prop1);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_notify_data, 2, sub_child, prop1);
_assert_int_val_child_prop (clip, val, 79, prop1, "GstFakeSink::num-buffers");
_assert_int_val_child_prop (child, val, 79, prop1,
"GstFakeSink::num-buffers");
g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL);
assert_equals_int (num_buffs, 79);
g_value_init (&val, G_TYPE_INT);
g_value_set_int (&val, 97);
fail_unless (ges_timeline_element_set_child_property (child,
"GstFakeSink::num-buffers", &val));
g_value_unset (&val);
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 0, NULL, NULL);
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
_assert_int_val_child_prop (clip, val, 97, prop1, "GstFakeSink::num-buffers");
_assert_int_val_child_prop (child, val, 97, prop1,
"GstFakeSink::num-buffers");
g_object_get (G_OBJECT (sub_child), "num-buffers", &num_buffs, NULL);
assert_equals_int (num_buffs, 97);
/* remove a property from the child, removes from the parent */
fail_unless (ges_timeline_element_remove_child_property (child, prop2));
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 1, sub_child, prop2);
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2);
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
fail_if (_has_child_property (child, prop2));
fail_if (_has_child_property (clip, prop2));
/* removing from parent doesn't remove from child */
fail_unless (ges_timeline_element_remove_child_property (clip, prop1));
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1);
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 1, sub_child, prop2);
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
fail_unless (_has_child_property (child, prop1));
fail_if (_has_child_property (clip, prop1));
/* but still safe to remove it from the child later */
fail_unless (ges_timeline_element_remove_child_property (child, prop1));
_assert_prop_changed_data (clip, clip_add_data, 3, sub_child, prop3);
_assert_prop_changed_data (clip, clip_remove_data, 2, sub_child, prop1);
_assert_prop_changed_data (clip, clip_notify_data, 3, sub_child, prop1);
_assert_prop_changed_data (child, child_add_data, 2, sub_child, prop2);
_assert_prop_changed_data (child, child_remove_data, 2, sub_child, prop1);
_assert_prop_changed_data (child, child_notify_data, 3, sub_child, prop1);
fail_if (_has_child_property (child, prop1));
fail_if (_has_child_property (clip, prop1));
gst_object_unref (sub_child);
gst_object_unref (timeline);
ges_deinit ();
}
GST_END_TEST;
static Suite *
ges_suite (void)
{
@ -944,6 +1249,8 @@ ges_suite (void)
tcase_add_test (tc_chain, test_effects_priorities);
tcase_add_test (tc_chain, test_children_time_setters);
tcase_add_test (tc_chain, test_can_add_effect);
tcase_add_test (tc_chain, test_children_properties_contain);
tcase_add_test (tc_chain, test_children_properties_change);
return s;
}

View file

@ -710,6 +710,141 @@ GST_START_TEST (test_group_serialization)
GST_END_TEST;
GST_START_TEST (test_children_properties_contain)
{
GESTimeline *timeline;
GESLayer *layer;
GESAsset *asset;
GESTimelineElement *c1, *c2, *c3, *g1, *g2;
GParamSpec **child_props1, **child_props2;
guint num_props1, num_props2;
ges_init ();
timeline = ges_timeline_new_audio_video ();
layer = ges_timeline_append_layer (timeline);
asset = ges_asset_request (GES_TYPE_TEST_CLIP, NULL, NULL);
/* choose one audio and one video to give them different properties */
c1 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 0, 0, 10,
GES_TRACK_TYPE_AUDIO));
c2 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 20, 0, 10,
GES_TRACK_TYPE_VIDEO));
/* but c3 will have the same child properties as c1! */
c3 = GES_TIMELINE_ELEMENT (ges_layer_add_asset (layer, asset, 40, 0, 10,
GES_TRACK_TYPE_AUDIO));
fail_unless (c1);
fail_unless (c2);
g1 = GES_TIMELINE_ELEMENT (ges_group_new ());
g2 = GES_TIMELINE_ELEMENT (ges_group_new ());
/* group should have the same as its children */
fail_unless (ges_container_add (GES_CONTAINER (g1), c1));
num_props1 = 0;
child_props1 = append_children_properties (NULL, c1, &num_props1);
num_props2 = 0;
child_props2 = append_children_properties (NULL, g1, &num_props2);
assert_property_list_match (child_props1, num_props1,
child_props2, num_props2);
/* add next child and gain its children properties as well */
fail_unless (ges_container_add (GES_CONTAINER (g1), c2));
/* add the child properties of c2 to the existing list for c1 */
child_props1 = append_children_properties (child_props1, c2, &num_props1);
free_children_properties (child_props2, num_props2);
num_props2 = 0;
child_props2 = append_children_properties (NULL, g1, &num_props2);
assert_property_list_match (child_props1, num_props1,
child_props2, num_props2);
/* FIXME: if c1 and c3 have the same child properties (they use the
* same GParamSpec) then ges_timeline_element_add_child_property_full
* will fail, even though the corresponding GObject child is not the
* same instance */
fail_unless (ges_container_add (GES_CONTAINER (g1), c3));
/* FIXME: regarding the above comment, ideally we would append the
* children properties for c3 to child_props1, so that its children
* properties appear twice in the list:
* child_props1 =
* append_children_properties (child_props1, c3, &num_props1); */
free_children_properties (child_props2, num_props2);
num_props2 = 0;
child_props2 = append_children_properties (NULL, g1, &num_props2);
assert_property_list_match (child_props1, num_props1,
child_props2, num_props2);
/* remove c3 */
fail_unless (ges_container_remove (GES_CONTAINER (g1), c3));
/* FIXME: regarding the above comment, ideally we would reset
* child_props1 to only contain the child properties for c1 and c2
* Currently, we at least want to make sure that the child properties
* for c1 remain.
* Currently, if we removed c1 first, all its children properties would
* be removed from g1, and this would *not* automatically register the
* children properties for c3. */
free_children_properties (child_props2, num_props2);
num_props2 = 0;
child_props2 = append_children_properties (NULL, g1, &num_props2);
assert_property_list_match (child_props1, num_props1,
child_props2, num_props2);
/* remove c1 */
fail_unless (ges_container_remove (GES_CONTAINER (g1), c1));
free_children_properties (child_props1, num_props1);
num_props1 = 0;
child_props1 = append_children_properties (NULL, c2, &num_props1);
free_children_properties (child_props2, num_props2);
num_props2 = 0;
child_props2 = append_children_properties (NULL, g1, &num_props2);
assert_property_list_match (child_props1, num_props1,
child_props2, num_props2);
/* add g1 and c1 to g2 */
fail_unless (ges_container_add (GES_CONTAINER (g2), g1));
fail_unless (ges_container_add (GES_CONTAINER (g2), c1));
free_children_properties (child_props1, num_props1);
num_props1 = 0;
child_props1 = append_children_properties (NULL, g2, &num_props1);
free_children_properties (child_props2, num_props2);
num_props2 = 0;
child_props2 = append_children_properties (NULL, c1, &num_props2);
child_props2 = append_children_properties (child_props2, g1, &num_props2);
assert_property_list_match (child_props1, num_props1,
child_props2, num_props2);
free_children_properties (child_props1, num_props1);
free_children_properties (child_props2, num_props2);
gst_object_unref (timeline);
ges_deinit ();
}
GST_END_TEST;
static Suite *
ges_suite (void)
{
@ -723,6 +858,7 @@ ges_suite (void)
tcase_add_test (tc_chain, test_group_in_self);
tcase_add_test (tc_chain, test_group_serialization);
tcase_add_test (tc_chain, test_group_in_group_layer_moving);
tcase_add_test (tc_chain, test_children_properties_contain);
return s;
}

View file

@ -312,3 +312,33 @@ print_timeline (GESTimeline * timeline)
g_printerr
("\n=====================================================================\n");
}
/* append the properties found in element to list, num_props should point
* to the current list length.
*/
GParamSpec **
append_children_properties (GParamSpec ** list, GESTimelineElement * element,
guint * num_props)
{
guint i, num;
GParamSpec **props =
ges_timeline_element_list_children_properties (element, &num);
fail_unless (props);
list = g_realloc_n (list, num + *num_props, sizeof (GParamSpec *));
for (i = 0; i < num; i++)
list[*num_props + i] = props[i];
g_free (props);
*num_props += num;
return list;
}
void
free_children_properties (GParamSpec ** list, guint num_props)
{
guint i;
for (i = 0; i < num_props; i++)
g_param_spec_unref (list[i]);
g_free (list);
}

View file

@ -51,6 +51,11 @@ ges_generate_test_file_audio_video (const gchar * filedest,
gboolean
play_timeline (GESTimeline * timeline);
GParamSpec **
append_children_properties (GParamSpec ** list, GESTimelineElement * element, guint * num_props);
void
free_children_properties (GParamSpec ** list, guint num_props);
#define nle_object_check(nleobj, start, duration, mstart, mduration, priority, active) { \
guint64 pstart, pdur, inpoint, pprio, pact; \
g_object_get (nleobj, "start", &pstart, "duration", &pdur, \
@ -123,6 +128,53 @@ G_STMT_START { \
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip), layer_prio); \
}
/* test that the two property lists contain the same properties the same
* number of times */
#define assert_property_list_match(list1, len1, list2, len2) \
{ \
gboolean *found_count_in_list2; \
guint *count_list1; \
guint i, j; \
found_count_in_list2 = g_new0 (gboolean, len1); \
count_list1 = g_new0 (guint, len1); \
for (i = 0; i < len1; i++) { \
found_count_in_list2[i] = 0; \
count_list1[i] = 0; \
for (j = 0; j < len1; j++) { \
if (list1[i] == list1[j]) \
count_list1[i] ++; \
} \
} \
for (j = 0; j < len2; j++) { \
guint count_list2 = 0; \
guint found_count_in_list1 = 0; \
GParamSpec *prop = list2[j]; \
for (i = 0; i < len2; i++) { \
if (list2[i] == prop) \
count_list2 ++; \
} \
for (i = 0; i < len1; i++) { \
if (list1[i] == prop) { \
found_count_in_list2[i] ++; \
found_count_in_list1 ++; \
} \
} \
fail_unless (found_count_in_list1 == count_list2, \
"Found property '%s' %u times, rather than %u times, in " #list1, \
prop->name, found_count_in_list1, count_list2); \
} \
/* make sure we found each one once */ \
for (i = 0; i < len1; i++) { \
GParamSpec *prop = list1[i]; \
fail_unless (found_count_in_list2[i] == count_list1[i], \
"Found property '%s' %u times, rather than %u times, in " #list2, \
prop->name, found_count_in_list2[i], count_list1[i]); \
} \
g_free (found_count_in_list2); \
g_free (count_list1); \
}
void print_timeline(GESTimeline *timeline);
#endif /* _GES_TEST_UTILS */