gstreamer/ges/ges-group.c
Thibault Saunier a46390ff56 Reimplement the timeline editing API
This is implemented on top of a Tree that represents the whole timeline.

SourceClips can not fully overlap anymore and the tests have been
updated to take that into account. Some new tests were added to verify
that behaviour in greater details
2019-03-15 23:51:55 +00:00

807 lines
25 KiB
C

/* GStreamer Editing Services
* Copyright (C) 2013 Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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.
*/
/**
* SECTION:gesgroup
* @title: GESGroup
* @short_description: Class that permits to group GESClip-s in a timeline,
* letting the user manage it a single GESTimelineElement
*
* A #GESGroup is an object which controls one or more
* #GESClips in one or more #GESLayer(s).
*
* To instanciate a group, you should use the ges_container_group method,
* this will be responsible for deciding what subclass of #GESContainer
* should be instaciated to group the various #GESTimelineElement passed
* in parametter.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ges-group.h"
#include "ges.h"
#include "ges-internal.h"
#include <string.h>
#define parent_class ges_group_parent_class
#define GES_CHILDREN_INIBIT_SIGNAL_EMISSION (GES_CHILDREN_LAST + 1)
#define GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT "ges-group-signals-ids-%p"
struct _GESGroupPrivate
{
gboolean reseting_start;
guint32 max_layer_prio;
/* This is used while were are setting ourselve a proper timing value,
* in this case the value should always be kept */
gboolean setting_value;
};
typedef struct
{
GESLayer *layer;
gulong child_clip_changed_layer_sid;
gulong child_priority_changed_sid;
gulong child_group_priority_changed_sid;
} ChildSignalIds;
enum
{
PROP_0,
PROP_START,
PROP_INPOINT,
PROP_DURATION,
PROP_MAX_DURATION,
PROP_PRIORITY,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST] = { NULL, };
G_DEFINE_TYPE_WITH_PRIVATE (GESGroup, ges_group, GES_TYPE_CONTAINER);
/****************************************************
* Our listening of children *
****************************************************/
static void
_update_our_values (GESGroup * group)
{
GList *tmp;
GESContainer *container = GES_CONTAINER (group);
guint32 min_layer_prio = G_MAXINT32, max_layer_prio = 0;
for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
GESContainer *child = tmp->data;
if (GES_IS_CLIP (child)) {
GESLayer *layer = ges_clip_get_layer (GES_CLIP (child));
gint32 prio;
if (!layer)
continue;
prio = ges_layer_get_priority (layer);
min_layer_prio = MIN (prio, min_layer_prio);
max_layer_prio = MAX (prio, max_layer_prio);
} else if (GES_IS_GROUP (child)) {
gint32 prio = _PRIORITY (child), height = GES_CONTAINER_HEIGHT (child);
min_layer_prio = MIN (prio, min_layer_prio);
max_layer_prio = MAX ((prio + height), max_layer_prio);
}
}
if (min_layer_prio != _PRIORITY (group)) {
group->priv->setting_value = TRUE;
_set_priority0 (GES_TIMELINE_ELEMENT (group), min_layer_prio);
group->priv->setting_value = FALSE;
for (tmp = GES_CONTAINER_CHILDREN (group); tmp; tmp = tmp->next) {
GESTimelineElement *child = tmp->data;
guint32 child_prio = GES_IS_CLIP (child) ?
GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) : _PRIORITY (child);
_ges_container_set_priority_offset (container,
child, min_layer_prio - child_prio);
}
}
group->priv->max_layer_prio = max_layer_prio;
_ges_container_set_height (GES_CONTAINER (group),
max_layer_prio - min_layer_prio + 1);
}
static void
_child_priority_changed_cb (GESLayer * layer,
GParamSpec * arg G_GNUC_UNUSED, GESTimelineElement * clip)
{
GESContainer *container = GES_CONTAINER (GES_TIMELINE_ELEMENT_PARENT (clip));
gint layer_prio = ges_layer_get_priority (layer);
gint offset = _ges_container_get_priority_offset (container, clip);
if (container->children_control_mode != GES_CHILDREN_UPDATE) {
GST_DEBUG_OBJECT (container, "Ignoring updated");
return;
}
if (layer_prio + offset == _PRIORITY (container))
return;
container->initiated_move = clip;
_set_priority0 (GES_TIMELINE_ELEMENT (container), layer_prio + offset);
container->initiated_move = NULL;
}
static void
_child_clip_changed_layer_cb (GESTimelineElement * clip,
GParamSpec * arg G_GNUC_UNUSED, GESGroup * group)
{
ChildSignalIds *sigids;
gchar *signals_ids_key;
GESLayer *old_layer, *new_layer;
gint offset, layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (clip);
GESContainer *container = GES_CONTAINER (group);
offset = _ges_container_get_priority_offset (container, clip);
signals_ids_key =
g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, clip);
sigids = g_object_get_data (G_OBJECT (group), signals_ids_key);
g_free (signals_ids_key);
old_layer = sigids->layer;
new_layer = ges_clip_get_layer (GES_CLIP (clip));
if (sigids->child_priority_changed_sid) {
g_signal_handler_disconnect (old_layer, sigids->child_priority_changed_sid);
sigids->child_priority_changed_sid = 0;
}
if (new_layer) {
sigids->child_priority_changed_sid =
g_signal_connect (new_layer, "notify::priority",
(GCallback) _child_priority_changed_cb, clip);
}
sigids->layer = new_layer;
if (container->children_control_mode != GES_CHILDREN_UPDATE) {
if (container->children_control_mode == GES_CHILDREN_INIBIT_SIGNAL_EMISSION) {
container->children_control_mode = GES_CHILDREN_UPDATE;
g_signal_stop_emission_by_name (clip, "notify::layer");
}
return;
}
if (new_layer && old_layer && (layer_prio + offset < 0 ||
(GES_TIMELINE_ELEMENT_TIMELINE (group) &&
layer_prio + offset + GES_CONTAINER_HEIGHT (group) - 1 >
g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers)))) {
GST_INFO_OBJECT (container,
"Trying to move to a layer %" GST_PTR_FORMAT " outside of"
"the timeline layers, moving back to old layer (prio %i)", new_layer,
_PRIORITY (group) - offset);
container->children_control_mode = GES_CHILDREN_INIBIT_SIGNAL_EMISSION;
ges_clip_move_to_layer (GES_CLIP (clip), old_layer);
g_signal_stop_emission_by_name (clip, "notify::layer");
return;
}
if (!new_layer || !old_layer) {
_update_our_values (group);
return;
}
container->initiated_move = clip;
_set_priority0 (GES_TIMELINE_ELEMENT (group), layer_prio + offset);
container->initiated_move = NULL;
}
static void
_child_group_priority_changed (GESTimelineElement * child,
GParamSpec * arg G_GNUC_UNUSED, GESGroup * group)
{
gint offset;
GESContainer *container = GES_CONTAINER (group);
if (container->children_control_mode != GES_CHILDREN_UPDATE) {
GST_DEBUG_OBJECT (group, "Ignoring updated");
return;
}
offset = _ges_container_get_priority_offset (container, child);
if (_PRIORITY (group) < offset ||
(GES_TIMELINE_ELEMENT_TIMELINE (group) &&
_PRIORITY (group) + offset + GES_CONTAINER_HEIGHT (group) >
g_list_length (GES_TIMELINE_ELEMENT_TIMELINE (group)->layers))) {
GST_WARNING_OBJECT (container, "Trying to move to a layer outside of"
"the timeline layers");
return;
}
container->initiated_move = child;
_set_priority0 (GES_TIMELINE_ELEMENT (group), _PRIORITY (child) + offset);
container->initiated_move = NULL;
}
/****************************************************
* GESTimelineElement vmethods *
****************************************************/
static gboolean
_trim (GESTimelineElement * group, GstClockTime start)
{
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
if (timeline == NULL) {
GST_DEBUG ("Not in a timeline yet");
return FALSE;
}
return timeline_tree_trim (timeline_get_tree (timeline), group,
0, GST_CLOCK_DIFF (start, _START (group)), GES_EDGE_START,
ges_timeline_get_snapping_distance (timeline));
}
static gboolean
_set_priority (GESTimelineElement * element, guint32 priority)
{
GList *tmp, *layers;
gint diff = priority - _PRIORITY (element);
GESContainer *container = GES_CONTAINER (element);
GESTimeline *timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
if (GES_GROUP (element)->priv->setting_value == TRUE)
return TRUE;
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
layers = GES_TIMELINE_ELEMENT_TIMELINE (element) ?
GES_TIMELINE_ELEMENT_TIMELINE (element)->layers : NULL;
if (layers == NULL) {
GST_WARNING_OBJECT (element, "Not any layer in the timeline, not doing"
"anything, timeline: %" GST_PTR_FORMAT,
GES_TIMELINE_ELEMENT_TIMELINE (element));
return FALSE;
}
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
GESTimelineElement *child = tmp->data;
if (child != container->initiated_move
|| ELEMENT_FLAG_IS_SET (container, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
if (GES_IS_CLIP (child)) {
guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child) + diff;
GESLayer *layer =
g_list_nth_data (GES_TIMELINE_GET_LAYERS (timeline), layer_prio);
if (layer == NULL) {
do {
layer = ges_timeline_append_layer (timeline);
} while (ges_layer_get_priority (layer) < layer_prio);
}
GST_DEBUG ("%" GES_FORMAT "moving from layer: %i to %i",
GES_ARGS (child), GES_TIMELINE_ELEMENT_LAYER_PRIORITY (child),
layer_prio);
ges_clip_move_to_layer (GES_CLIP (child), g_list_nth_data (layers,
layer_prio));
} else if (GES_IS_GROUP (child)) {
GST_DEBUG_OBJECT (child, "moving from %i to %i",
_PRIORITY (child), diff + _PRIORITY (child));
ges_timeline_element_set_priority (child, diff + _PRIORITY (child));
}
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
static gboolean
_set_start (GESTimelineElement * element, GstClockTime start)
{
GList *tmp;
gint64 diff = start - _START (element);
GESTimeline *timeline;
GESContainer *container = GES_CONTAINER (element);
GESTimelineElement *toplevel =
ges_timeline_element_get_toplevel_parent (element);;
gst_object_unref (toplevel);
if (GES_GROUP (element)->priv->setting_value == TRUE)
/* Let GESContainer update itself */
return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_start (element,
start);
if (ELEMENT_FLAG_IS_SET (element, GES_TIMELINE_ELEMENT_SET_SIMPLE) ||
ELEMENT_FLAG_IS_SET (toplevel, GES_TIMELINE_ELEMENT_SET_SIMPLE)) {
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next)
_set_start0 (tmp->data, _START (tmp->data) + diff);
container->children_control_mode = GES_CHILDREN_UPDATE;
return TRUE;
}
timeline = GES_TIMELINE_ELEMENT_TIMELINE (element);
if (timeline)
return ges_timeline_move_object_simple (timeline, element, NULL,
GES_EDGE_NONE, start);
return TRUE;
}
static gboolean
_set_inpoint (GESTimelineElement * element, GstClockTime inpoint)
{
return FALSE;
}
static gboolean
_set_duration (GESTimelineElement * element, GstClockTime duration)
{
GList *tmp;
GstClockTime last_child_end = 0, new_end;
GESContainer *container = GES_CONTAINER (element);
GESGroupPrivate *priv = GES_GROUP (element)->priv;
if (priv->setting_value == TRUE)
/* Let GESContainer update itself */
return GES_TIMELINE_ELEMENT_CLASS (parent_class)->set_duration (element,
duration);
if (element->timeline
&& !timeline_tree_can_move_element (timeline_get_tree (element->timeline),
element, _PRIORITY (element), element->start, duration, NULL)) {
return FALSE;
}
if (container->initiated_move == NULL) {
gboolean expending = (_DURATION (element) < duration);
new_end = _START (element) + duration;
container->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
GESTimelineElement *child = tmp->data;
GstClockTime n_dur;
if ((!expending && _END (child) > new_end) ||
(expending && (_END (child) >= _END (element)))) {
n_dur = MAX (0, ((gint64) (new_end - _START (child))));
_set_duration0 (child, n_dur);
}
}
container->children_control_mode = GES_CHILDREN_UPDATE;
}
for (tmp = GES_CONTAINER_CHILDREN (element); tmp; tmp = tmp->next) {
if (_DURATION (tmp->data))
last_child_end =
MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end);
}
priv->setting_value = TRUE;
_set_duration0 (element, last_child_end - _START (element));
priv->setting_value = FALSE;
return FALSE;
}
/****************************************************
* *
* GESContainer virtual methods implementation *
* *
****************************************************/
static gboolean
_add_child (GESContainer * group, GESTimelineElement * child)
{
g_return_val_if_fail (GES_IS_CONTAINER (child), FALSE);
return TRUE;
}
static void
_child_added (GESContainer * group, GESTimelineElement * child)
{
GList *children, *tmp;
gchar *signals_ids_key;
ChildSignalIds *signals_ids;
GESGroupPrivate *priv = GES_GROUP (group)->priv;
GstClockTime last_child_end = 0, first_child_start = G_MAXUINT64;
if (!GES_TIMELINE_ELEMENT_TIMELINE (group)
&& GES_TIMELINE_ELEMENT_TIMELINE (child)) {
timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (child),
GES_GROUP (group));
timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE (child),
GES_GROUP (group));
}
children = GES_CONTAINER_CHILDREN (group);
for (tmp = children; tmp; tmp = tmp->next) {
last_child_end = MAX (GES_TIMELINE_ELEMENT_END (tmp->data), last_child_end);
first_child_start =
MIN (GES_TIMELINE_ELEMENT_START (tmp->data), first_child_start);
}
priv->setting_value = TRUE;
ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
if (first_child_start != GES_TIMELINE_ELEMENT_START (group)) {
group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
_set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
}
if (last_child_end != GES_TIMELINE_ELEMENT_END (group)) {
_set_duration0 (GES_TIMELINE_ELEMENT (group),
last_child_end - first_child_start);
}
ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
priv->setting_value = FALSE;
group->children_control_mode = GES_CHILDREN_UPDATE;
_update_our_values (GES_GROUP (group));
signals_ids_key =
g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, child);
signals_ids = g_malloc0 (sizeof (ChildSignalIds));
g_object_set_data_full (G_OBJECT (group), signals_ids_key,
signals_ids, g_free);
g_free (signals_ids_key);
if (GES_IS_CLIP (child)) {
GESLayer *layer = ges_clip_get_layer (GES_CLIP (child));
signals_ids->child_clip_changed_layer_sid =
g_signal_connect (child, "notify::layer",
(GCallback) _child_clip_changed_layer_cb, group);
if (layer) {
signals_ids->child_priority_changed_sid = g_signal_connect (layer,
"notify::priority", (GCallback) _child_priority_changed_cb, child);
}
signals_ids->layer = layer;
} else if (GES_IS_GROUP (child), group) {
signals_ids->child_group_priority_changed_sid =
g_signal_connect (child, "notify::priority",
(GCallback) _child_group_priority_changed, group);
}
}
static void
_disconnect_signals (GESGroup * group, GESTimelineElement * child,
ChildSignalIds * sigids)
{
if (sigids->child_group_priority_changed_sid) {
g_signal_handler_disconnect (child,
sigids->child_group_priority_changed_sid);
sigids->child_group_priority_changed_sid = 0;
}
if (sigids->child_clip_changed_layer_sid) {
g_signal_handler_disconnect (child, sigids->child_clip_changed_layer_sid);
sigids->child_clip_changed_layer_sid = 0;
}
if (sigids->child_priority_changed_sid) {
g_signal_handler_disconnect (sigids->layer,
sigids->child_priority_changed_sid);
sigids->child_priority_changed_sid = 0;
}
}
static void
_child_removed (GESContainer * group, GESTimelineElement * child)
{
GList *children;
GstClockTime first_child_start;
gchar *signals_ids_key;
ChildSignalIds *sigids;
GESGroupPrivate *priv = GES_GROUP (group)->priv;
_ges_container_sort_children (group);
children = GES_CONTAINER_CHILDREN (group);
signals_ids_key =
g_strdup_printf (GES_GROUP_SIGNALS_IDS_DATA_KEY_FORMAT, child);
sigids = g_object_get_data (G_OBJECT (group), signals_ids_key);
_disconnect_signals (GES_GROUP (group), child, sigids);
g_free (signals_ids_key);
if (children == NULL) {
GST_FIXME_OBJECT (group, "Auto destroy myself?");
if (GES_TIMELINE_ELEMENT_TIMELINE (group))
timeline_remove_group (GES_TIMELINE_ELEMENT_TIMELINE (group),
GES_GROUP (group));
return;
}
priv->setting_value = TRUE;
ELEMENT_SET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
first_child_start = GES_TIMELINE_ELEMENT_START (children->data);
if (first_child_start > GES_TIMELINE_ELEMENT_START (group)) {
group->children_control_mode = GES_CHILDREN_IGNORE_NOTIFIES;
_set_start0 (GES_TIMELINE_ELEMENT (group), first_child_start);
group->children_control_mode = GES_CHILDREN_UPDATE;
}
ELEMENT_UNSET_FLAG (group, GES_TIMELINE_ELEMENT_SET_SIMPLE);
priv->setting_value = FALSE;
}
static GList *
_ungroup (GESContainer * group, gboolean recursive)
{
GPtrArray *children_array;
GESTimeline *timeline;
GList *children, *tmp, *ret = NULL;
/* We choose 16 just as an arbitrary value */
children_array = g_ptr_array_sized_new (16);
timeline = GES_TIMELINE_ELEMENT_TIMELINE (group);
children = ges_container_get_children (group, FALSE);
for (tmp = children; tmp; tmp = tmp->next) {
GESTimelineElement *child = tmp->data;
gst_object_ref (child);
ges_container_remove (group, child);
g_ptr_array_add (children_array, child);
ret = g_list_append (ret, child);
}
if (timeline)
timeline_emit_group_removed (timeline, (GESGroup *) group, children_array);
g_ptr_array_free (children_array, TRUE);
g_list_free_full (children, gst_object_unref);
/* No need to remove from the timeline here, this will be done in _child_removed */
return ret;
}
static GESContainer *
_group (GList * containers)
{
GList *tmp;
GESTimeline *timeline = NULL;
GESContainer *ret = g_object_new (GES_TYPE_GROUP, NULL);
if (!containers)
return ret;
for (tmp = containers; tmp; tmp = tmp->next) {
if (!timeline) {
timeline = GES_TIMELINE_ELEMENT_TIMELINE (tmp->data);
} else if (timeline != GES_TIMELINE_ELEMENT_TIMELINE (tmp->data)) {
g_object_unref (ret);
return NULL;
}
ges_container_add (ret, tmp->data);
}
/* No need to add to the timeline here, this will be done in _child_added */
return ret;
}
static GESTimelineElement *
_paste (GESTimelineElement * element, GESTimelineElement * ref,
GstClockTime paste_position)
{
GESTimelineElement *ngroup =
GES_TIMELINE_ELEMENT_CLASS (parent_class)->paste (element, ref,
paste_position);
if (ngroup) {
if (GES_CONTAINER_CHILDREN (ngroup)) {
timeline_add_group (GES_TIMELINE_ELEMENT_TIMELINE (GES_CONTAINER_CHILDREN
(ngroup)->data), GES_GROUP (element));
timeline_emit_group_added (GES_TIMELINE_ELEMENT_TIMELINE
(GES_CONTAINER_CHILDREN (ngroup)->data), GES_GROUP (element));
}
}
return ngroup;
}
/****************************************************
* *
* GObject virtual methods implementation *
* *
****************************************************/
static void
ges_group_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);
switch (property_id) {
case PROP_START:
g_value_set_uint64 (value, self->start);
break;
case PROP_INPOINT:
g_value_set_uint64 (value, self->inpoint);
break;
case PROP_DURATION:
g_value_set_uint64 (value, self->duration);
break;
case PROP_MAX_DURATION:
g_value_set_uint64 (value, self->maxduration);
break;
case PROP_PRIORITY:
g_value_set_uint (value, self->priority);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
}
}
static void
ges_group_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
GESTimelineElement *self = GES_TIMELINE_ELEMENT (object);
switch (property_id) {
case PROP_START:
ges_timeline_element_set_start (self, g_value_get_uint64 (value));
break;
case PROP_INPOINT:
ges_timeline_element_set_inpoint (self, g_value_get_uint64 (value));
break;
case PROP_DURATION:
ges_timeline_element_set_duration (self, g_value_get_uint64 (value));
break;
case PROP_PRIORITY:
ges_timeline_element_set_priority (self, g_value_get_uint (value));
break;
case PROP_MAX_DURATION:
ges_timeline_element_set_max_duration (self, g_value_get_uint64 (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
}
}
static void
ges_group_class_init (GESGroupClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GESContainerClass *container_class = GES_CONTAINER_CLASS (klass);
GESTimelineElementClass *element_class = GES_TIMELINE_ELEMENT_CLASS (klass);
object_class->get_property = ges_group_get_property;
object_class->set_property = ges_group_set_property;
element_class->trim = _trim;
element_class->set_duration = _set_duration;
element_class->set_inpoint = _set_inpoint;
element_class->set_start = _set_start;
element_class->set_priority = _set_priority;
element_class->paste = _paste;
/* We override start, inpoint, duration and max-duration from GESTimelineElement
* in order to makes sure those fields are not serialized.
*/
/**
* GESGroup:start:
*
* The position of the object in its container (in nanoseconds).
*/
properties[PROP_START] = g_param_spec_uint64 ("start", "Start",
"The position in the container", 0, G_MAXUINT64, 0,
G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
/**
* GESGroup:in-point:
*
* The in-point at which this #GESGroup will start outputting data
* from its contents (in nanoseconds).
*
* Ex : an in-point of 5 seconds means that the first outputted buffer will
* be the one located 5 seconds in the controlled resource.
*/
properties[PROP_INPOINT] =
g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0,
G_MAXUINT64, 0, G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
/**
* GESGroup:duration:
*
* The duration (in nanoseconds) which will be used in the container
*/
properties[PROP_DURATION] =
g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0,
G_MAXUINT64, GST_CLOCK_TIME_NONE,
G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
/**
* GESGroup:max-duration:
*
* The maximum duration (in nanoseconds) of the #GESGroup.
*/
properties[PROP_MAX_DURATION] =
g_param_spec_uint64 ("max-duration", "Maximum duration",
"The maximum duration of the object", 0, G_MAXUINT64, GST_CLOCK_TIME_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | GES_PARAM_NO_SERIALIZATION);
/**
* GESTGroup:priority:
*
* The priority of the object.
*/
properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority",
"The priority of the object", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | GES_PARAM_NO_SERIALIZATION);
g_object_class_install_properties (object_class, PROP_LAST, properties);
container_class->add_child = _add_child;
container_class->child_added = _child_added;
container_class->child_removed = _child_removed;
container_class->ungroup = _ungroup;
container_class->group = _group;
container_class->grouping_priority = 0;
}
static void
ges_group_init (GESGroup * self)
{
self->priv = ges_group_get_instance_private (self);
self->priv->setting_value = FALSE;
}
/****************************************************
* *
* API implementation *
* *
****************************************************/
/**
* ges_group_new:
*
* Created a new empty #GESGroup, if you want to group several container
* together, it is recommanded to use the #ges_container_group method so the
* proper subclass is selected.
*
* Returns: (transfer floating): The new empty group.
*/
GESGroup *
ges_group_new (void)
{
return g_object_new (GES_TYPE_GROUP, NULL);
}