ges: Add a way to set layer activeness by track
authorThibault Saunier <tsaunier@igalia.com>
Fri, 6 Mar 2020 21:56:52 +0000 (18:56 -0300)
committerThibault Saunier <tsaunier@igalia.com>
Wed, 25 Mar 2020 18:40:25 +0000 (15:40 -0300)
a.k.a muting layers.

Adding unit tests and making sure serialization works properly

15 files changed:
ges/ges-base-xml-formatter.c
ges/ges-internal.h
ges/ges-layer.c
ges/ges-layer.h
ges/ges-timeline-tree.c
ges/ges-timeline-tree.h
ges/ges-timeline.c
ges/ges-track-element.c
ges/ges-track.c
ges/ges-validate.c
ges/ges-xml-formatter.c
tests/check/meson.build
tests/check/python/common.py
tests/check/python/test_timeline.py
tests/check/scenarios/check_layer_activness_gaps.scenario [new file with mode: 0644]

index 5d8708e86d19794cfad10955d66801472a34e4c1..a8565b596eb880531e34783ad463a66ee83d9ec0 100644 (file)
@@ -943,7 +943,7 @@ ges_base_xml_formatter_set_timeline_properties (GESBaseXmlFormatter * self,
 void
 ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
     GType extractable_type, guint priority, GstStructure * properties,
-    const gchar * metadatas, GError ** error)
+    const gchar * metadatas, gchar ** deactivated_tracks, GError ** error)
 {
   LayerEntry *entry;
   GESAsset *asset;
@@ -967,10 +967,11 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
             G_MARKUP_ERROR_INVALID_CONTENT,
             "Layer type %s could not be created'",
             g_type_name (extractable_type));
-        return;
       }
+      return;
     }
     layer = GES_LAYER (ges_asset_extract (asset, error));
+    gst_object_unref (asset);
   }
 
   ges_layer_set_priority (layer, priority);
@@ -988,6 +989,27 @@ ges_base_xml_formatter_add_layer (GESBaseXmlFormatter * self,
     ges_meta_container_add_metas_from_string (GES_META_CONTAINER (layer),
         metadatas);
 
+  if (deactivated_tracks) {
+    gint i;
+    GList *tracks = NULL;
+
+    for (i = 0; deactivated_tracks[i] && deactivated_tracks[i][0] != '\0'; i++) {
+      GESTrack *track =
+          g_hash_table_lookup (priv->tracks, deactivated_tracks[i]);
+
+      if (!track) {
+        GST_ERROR_OBJECT (self,
+            "Unknown deactivated track: %s", deactivated_tracks[i]);
+        continue;
+      }
+
+      tracks = g_list_append (tracks, track);
+    }
+
+    ges_layer_set_active_for_tracks (layer, FALSE, tracks);
+    g_list_free (tracks);
+  }
+
   entry = g_slice_new0 (LayerEntry);
   entry->layer = gst_object_ref (layer);
   entry->auto_trans = auto_transition;
index c711a97034f9f8ff2157910fa9e9811469d8806b..07fd8734458d78aa2647d5333f4db84b54492333 100644 (file)
@@ -291,6 +291,7 @@ G_GNUC_INTERNAL void ges_base_xml_formatter_add_layer           (GESBaseXmlForma
                                                                  guint priority,
                                                                  GstStructure *properties,
                                                                  const gchar *metadatas,
+                                                                 gchar **deactivated_tracks,
                                                                  GError **error);
 G_GNUC_INTERNAL void ges_base_xml_formatter_add_track           (GESBaseXmlFormatter *self,
                                                                  GESTrackType track_type,
@@ -418,6 +419,7 @@ G_GNUC_INTERNAL void layer_set_priority               (GESLayer * layer, guint p
 G_GNUC_INTERNAL gboolean  ges_track_element_set_track           (GESTrackElement * object, GESTrack * track);
 G_GNUC_INTERNAL void ges_track_element_copy_properties          (GESTimelineElement * element,
                                                                  GESTimelineElement * elementcopy);
+G_GNUC_INTERNAL void ges_track_element_set_layer_active         (GESTrackElement *element, gboolean active);
 
 G_GNUC_INTERNAL void ges_track_element_copy_bindings (GESTrackElement *element,
                                                       GESTrackElement *new_element,
index 9e510102785153d48ca1aeed74a7a7b3e0fa90ae..b5a3a4460a255ba170f71b896c86e33e7ab57a57 100644 (file)
@@ -65,6 +65,8 @@ struct _GESLayerPrivate
   guint32 priority;             /* The priority of the layer within the
                                  * containing timeline */
   gboolean auto_transition;
+
+  GHashTable *tracks_activness;
 };
 
 typedef struct
@@ -73,6 +75,43 @@ typedef struct
   GESLayer *layer;
 } NewAssetUData;
 
+typedef struct
+{
+  GESTrack *track;
+  GESLayer *layer;
+  gboolean active;
+  gboolean track_disposed;
+} LayerActivnessData;
+
+static void
+_track_disposed_cb (LayerActivnessData * data, GObject * disposed_track)
+{
+  data->track_disposed = TRUE;
+  g_hash_table_remove (data->layer->priv->tracks_activness, data->track);
+}
+
+static void
+layer_activness_data_free (LayerActivnessData * data)
+{
+  if (!data->track_disposed)
+    g_object_weak_unref ((GObject *) data->track,
+        (GWeakNotify) _track_disposed_cb, data);
+  g_free (data);
+}
+
+static LayerActivnessData *
+layer_activness_data_new (GESTrack * track, GESLayer * layer, gboolean active)
+{
+  LayerActivnessData *data = g_new0 (LayerActivnessData, 1);
+
+  data->layer = layer;
+  data->track = track;
+  data->active = active;
+  g_object_weak_ref (G_OBJECT (track), (GWeakNotify) _track_disposed_cb, data);
+
+  return data;
+}
+
 enum
 {
   PROP_0,
@@ -85,6 +124,7 @@ enum
 {
   OBJECT_ADDED,
   OBJECT_REMOVED,
+  ACTIVE_CHANGED,
   LAST_SIGNAL
 };
 
@@ -145,6 +185,8 @@ ges_layer_dispose (GObject * object)
   while (priv->clips_start)
     ges_layer_remove_clip (layer, (GESClip *) priv->clips_start->data);
 
+  g_clear_pointer (&layer->priv->tracks_activness, g_hash_table_unref);
+
   G_OBJECT_CLASS (ges_layer_parent_class)->dispose (object);
 }
 
@@ -245,6 +287,21 @@ ges_layer_class_init (GESLayerClass * klass)
       g_signal_new ("clip-removed", G_TYPE_FROM_CLASS (klass),
       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GESLayerClass,
           object_removed), NULL, NULL, NULL, G_TYPE_NONE, 1, GES_TYPE_CLIP);
+
+  /**
+   * GESLayer::active-changed:
+   * @layer: The #GESLayer
+   * @active: Whether @layer has been made active or de-active in the @tracks
+   * @tracks: (element-type GESTrack) (transfer none): A list of #GESTrack
+   * which have been activated or deactivated
+   *
+   * Will be emitted whenever the layer is activated or deactivated
+   * for some #GESTrack. See ges_layer_set_active_for_tracks().
+   */
+  ges_layer_signals[ACTIVE_CHANGED] =
+      g_signal_new ("active-changed", G_TYPE_FROM_CLASS (klass),
+      G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2,
+      G_TYPE_BOOLEAN, G_TYPE_PTR_ARRAY);
 }
 
 static void
@@ -257,6 +314,10 @@ ges_layer_init (GESLayer * self)
   self->min_nle_priority = MIN_NLE_PRIO;
   self->max_nle_priority = LAYER_HEIGHT + MIN_NLE_PRIO;
 
+  self->priv->tracks_activness =
+      g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, (GDestroyNotify) layer_activness_data_free);
+
   _register_metas (self);
 }
 
@@ -420,6 +481,7 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
     gboolean emit_removed)
 {
   GESLayer *current_layer;
+  GList *tmp;
 
   GST_DEBUG ("layer:%p, clip:%p", layer, clip);
 
@@ -448,6 +510,9 @@ ges_layer_remove_clip_internal (GESLayer * layer, GESClip * clip,
   if (layer->timeline)
     ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (clip), NULL);
 
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next)
+    ges_track_element_set_layer_active (tmp->data, TRUE);
+
   /* Remove our reference to the clip */
   gst_object_unref (clip);
 
@@ -618,6 +683,7 @@ ges_layer_is_empty (GESLayer * layer)
 gboolean
 ges_layer_add_clip (GESLayer * layer, GESClip * clip)
 {
+  GList *tmp;
   GESAsset *asset;
   GESLayerPrivate *priv;
   GESLayer *current_layer;
@@ -723,6 +789,14 @@ ges_layer_add_clip (GESLayer * layer, GESClip * clip)
     return FALSE;
   }
 
+  for (tmp = GES_CONTAINER_CHILDREN (clip); tmp; tmp = tmp->next) {
+    GESTrack *track = ges_track_element_get_track (tmp->data);
+
+    if (track)
+      ges_track_element_set_layer_active (tmp->data,
+          ges_layer_get_active_for_track (layer, track));
+  }
+
   return TRUE;
 }
 
@@ -879,3 +953,87 @@ ges_layer_get_clips_in_interval (GESLayer * layer, GstClockTime start,
   }
   return intersecting_clips;
 }
+
+/**
+ * ges_layer_get_active_for_track:
+ * @layer: The #GESLayer
+ * @track: The #GESTrack to check if @layer is currently active for
+ *
+ * Gets whether the layer is active for the given track. See
+ * ges_layer_set_active_for_tracks().
+ *
+ * Returns: %TRUE if @layer is active for @track, or %FALSE otherwise.
+ */
+gboolean
+ges_layer_get_active_for_track (GESLayer * layer, GESTrack * track)
+{
+  LayerActivnessData *d;
+
+  g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+  g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
+  g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track),
+      FALSE);
+
+  d = g_hash_table_lookup (layer->priv->tracks_activness, track);
+
+  return d ? d->active : TRUE;
+}
+
+/**
+ * ges_layer_set_active_for_tracks:
+ * @layer: The #GESLayer
+ * @active: Whether elements in @tracks should be active or not
+ * @tracks: (transfer none) (element-type GESTrack) (allow-none): The list of
+ * tracks @layer should be (de-)active in, or %NULL to include all the tracks
+ * in the @layer's timeline
+ *
+ * Activate or deactivate track elements in @tracks (or in all tracks if @tracks
+ * is %NULL).
+ *
+ * When a layer is deactivated for a track, all the #GESTrackElement-s in
+ * the track that belong to a #GESClip in the layer will no longer be
+ * active in the track, regardless of their individual
+ * #GESTrackElement:active value.
+ *
+ * Note that by default a layer will be active for all of its
+ * timeline's tracks.
+ *
+ * Returns: %TRUE if the operation worked %FALSE otherwise.
+ */
+gboolean
+ges_layer_set_active_for_tracks (GESLayer * layer, gboolean active,
+    GList * tracks)
+{
+  GList *tmp, *owned_tracks = NULL;
+  GPtrArray *changed_tracks = NULL;
+
+  g_return_val_if_fail (GES_IS_LAYER (layer), FALSE);
+
+  if (!tracks && layer->timeline)
+    owned_tracks = tracks = ges_timeline_get_tracks (layer->timeline);
+
+  for (tmp = tracks; tmp; tmp = tmp->next) {
+    GESTrack *track = tmp->data;
+
+    /* Handle setting timeline later */
+    g_return_val_if_fail (layer->timeline == ges_track_get_timeline (track),
+        FALSE);
+
+    if (ges_layer_get_active_for_track (layer, track) != active) {
+      if (changed_tracks == NULL)
+        changed_tracks = g_ptr_array_new ();
+      g_ptr_array_add (changed_tracks, track);
+    }
+    g_hash_table_insert (layer->priv->tracks_activness, track,
+        layer_activness_data_new (track, layer, active));
+  }
+
+  if (changed_tracks) {
+    g_signal_emit (layer, ges_layer_signals[ACTIVE_CHANGED], 0, active,
+        changed_tracks);
+    g_ptr_array_unref (changed_tracks);
+  }
+  g_list_free_full (owned_tracks, gst_object_unref);
+
+  return TRUE;
+}
index a37ea445b0ca70a9b68ea5f88d865a39e133880b..7e7a8629d18e311df0e3eb18f75d3d0b2abdfdff 100644 (file)
@@ -121,5 +121,11 @@ GES_API
 GList*   ges_layer_get_clips   (GESLayer * layer);
 GES_API
 GstClockTime ges_layer_get_duration (GESLayer *layer);
+GES_API
+gboolean ges_layer_set_active_for_tracks(GESLayer *layer, gboolean active,
+                                         GList *tracks);
+
+GES_API gboolean ges_layer_get_active_for_track(GESLayer *layer,
+                                                GESTrack *track);
 
 G_END_DECLS
index 8956ff74b3a606f6b84d68051dd3e1a179e1e6f4..93be05227aa285013d5c5de84da45d99a6281d4e 100644 (file)
@@ -1333,3 +1333,30 @@ timeline_tree_get_duration (GNode * root)
 
   return duration;
 }
+
+static gboolean
+reset_layer_activness (GNode * node, GESLayer * layer)
+{
+  GESTrack *track;
+
+
+  if (!GES_IS_TRACK_ELEMENT (node->data))
+    return FALSE;
+
+  track = ges_track_element_get_track (node->data);
+  if (!track || (ges_timeline_element_get_layer_priority (node->data) !=
+          ges_layer_get_priority (layer)))
+    return FALSE;
+
+  ges_track_element_set_layer_active (node->data,
+      ges_layer_get_active_for_track (layer, track));
+
+  return FALSE;
+}
+
+void
+timeline_tree_reset_layer_active (GNode * root, GESLayer * layer)
+{
+  g_node_traverse (root, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
+      (GNodeTraverseFunc) reset_layer_activness, layer);
+}
index afa8d1134fde2dcf1fd56c9491040454644a0c49..a65a19f0aff21257fa0472f94b0b36545d535cf2 100644 (file)
@@ -73,4 +73,6 @@ ges_timeline_find_auto_transition         (GESTimeline * timeline, GESTrackEleme
 void
 timeline_update_duration                  (GESTimeline * timeline);
 
+void timeline_tree_reset_layer_active     (GNode *root, GESLayer *layer);
+
 void timeline_tree_init_debug             (void);
index 7f541708af346effd4475f1e59dd31a0d380f96c..bf68d63f78b6fb890445501cf75645367ef4f39c 100644 (file)
@@ -1350,6 +1350,13 @@ add_object_to_tracks (GESTimeline * timeline, GESClip * clip, GESTrack * track)
   UNLOCK_DYN (timeline);
 }
 
+static void
+layer_active_changed_cb (GESLayer * layer, gboolean active G_GNUC_UNUSED,
+    GPtrArray * tracks G_GNUC_UNUSED, GESTimeline * timeline)
+{
+  timeline_tree_reset_layer_active (timeline->priv->tree, layer);
+}
+
 static void
 layer_auto_transition_changed_cb (GESLayer * layer,
     GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline)
@@ -2050,6 +2057,8 @@ ges_timeline_add_layer (GESTimeline * timeline, GESLayer * layer)
       G_CALLBACK (layer_priority_changed_cb), timeline);
   g_signal_connect (layer, "notify::auto-transition",
       G_CALLBACK (layer_auto_transition_changed_cb), timeline);
+  g_signal_connect_after (layer, "active-changed",
+      G_CALLBACK (layer_active_changed_cb), timeline);
 
   GST_DEBUG ("Done adding layer, emitting 'layer-added' signal");
   g_signal_emit (timeline, ges_timeline_signals[LAYER_ADDED], 0, layer);
@@ -2111,6 +2120,8 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESLayer * layer)
       timeline);
   g_signal_handlers_disconnect_by_func (layer,
       layer_auto_transition_changed_cb, timeline);
+  g_signal_handlers_disconnect_by_func (layer, layer_active_changed_cb,
+      timeline);
 
   timeline->layers = g_list_remove (timeline->layers, layer);
   ges_layer_set_timeline (layer, NULL);
index 666e5632651923856d463022ed4398968a0778c2..e69971e0c2c709f58a1e2ad6dba8922d4820a368 100644 (file)
@@ -63,6 +63,7 @@ struct _GESTrackElementPrivate
 
   gboolean locked;              /* If TRUE, then moves in sync with its controlling
                                  * GESClip */
+  gboolean layer_active;
 
   GHashTable *bindings_hashtable;       /* We need this if we want to be able to serialize
                                            and deserialize keyframes */
@@ -302,7 +303,7 @@ ges_track_element_constructed (GObject * gobject)
       "inpoint", GES_TIMELINE_ELEMENT_INPOINT (object),
       "duration", GES_TIMELINE_ELEMENT_DURATION (object),
       "priority", GES_TIMELINE_ELEMENT_PRIORITY (object),
-      "active", object->active, NULL);
+      "active", object->active & object->priv->layer_active, NULL);
 
   media_duration_factor =
       ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT
@@ -467,6 +468,7 @@ ges_track_element_init (GESTrackElement * self)
   GES_TIMELINE_ELEMENT_DURATION (self) = GST_SECOND;
   GES_TIMELINE_ELEMENT_PRIORITY (self) = 0;
   self->active = TRUE;
+  self->priv->layer_active = TRUE;
 
   priv->bindings_hashtable = g_hash_table_new_full (g_str_hash, g_str_equal,
       g_free, NULL);
@@ -740,7 +742,8 @@ ges_track_element_set_active (GESTrackElement * object, gboolean active)
   if (G_UNLIKELY (active == object->active))
     return FALSE;
 
-  g_object_set (object->priv->nleobject, "active", active, NULL);
+  g_object_set (object->priv->nleobject, "active",
+      active & object->priv->layer_active, NULL);
 
   object->active = active;
   if (GES_TRACK_ELEMENT_GET_CLASS (object)->active_changed)
@@ -1050,6 +1053,17 @@ ges_track_element_set_track (GESTrackElement * object, GESTrack * track)
   return ret;
 }
 
+void
+ges_track_element_set_layer_active (GESTrackElement * element, gboolean active)
+{
+  if (element->priv->layer_active == active)
+    return;
+
+  element->priv->layer_active = active;
+  g_object_set (element->priv->nleobject, "active", active & element->active,
+      NULL);
+}
+
 /**
  * ges_track_element_get_all_control_bindings
  * @trackelement: A #GESTrackElement
index 4c0b52215d6e0696c4b1589c7afe332c99d519a2..af57dba018edfc9a9eb4f894ba7997cccf258687 100644 (file)
@@ -232,6 +232,17 @@ update_gaps (GESTrack * track)
     if (!ges_track_element_is_active (trackelement))
       continue;
 
+    if (priv->timeline) {
+      guint32 layer_prio = GES_TIMELINE_ELEMENT_LAYER_PRIORITY (trackelement);
+
+      if (layer_prio != GES_TIMELINE_ELEMENT_NO_LAYER_PRIORITY) {
+        GESLayer *layer = g_list_nth_data (priv->timeline->layers, layer_prio);
+
+        if (!ges_layer_get_active_for_track (layer, track))
+          continue;
+      }
+    }
+
     start = _START (trackelement);
     end = start + _DURATION (trackelement);
 
index af449b5680846cd768b05e22a7d46c7f22585e6d..cbdf153795a4aadc1df599d3d3d3e37bf7e642b7 100644 (file)
@@ -1066,6 +1066,74 @@ done:
   return res;
 }
 
+static gint
+set_layer_active (GstValidateScenario * scenario, GstValidateAction * action)
+{
+  gboolean active;
+  gint i, layer_prio;
+  GESLayer *layer;
+  GList *tracks = NULL;
+  GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK;
+  gchar **track_names =
+      gst_validate_utils_get_strv (action->structure, "tracks");
+
+  DECLARE_AND_GET_TIMELINE (scenario, action);
+
+  for (i = 0; track_names[i]; i++) {
+    GESTrack *track =
+        (GESTrack *) gst_bin_get_by_name (GST_BIN (timeline), track_names[i]);
+
+    if (!track) {
+      GST_VALIDATE_REPORT_ACTION (scenario, action,
+          SCENARIO_ACTION_EXECUTION_ERROR,
+          "Could not find track %s", track_names[i]);
+      res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+      goto done;
+    }
+
+    tracks = g_list_prepend (tracks, track);
+  }
+
+  if (!gst_structure_get_int (action->structure, "layer-priority", &layer_prio)) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not find layer from %" GST_PTR_FORMAT, action->structure);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+  if (!(layer = g_list_nth_data (timeline->layers, layer_prio))) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR, "Could not find layer %d", layer_prio);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+
+  if (!gst_structure_get_boolean (action->structure, "active", &active)) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not find 'active' boolean in %" GST_PTR_FORMAT,
+        action->structure);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+
+  if (!ges_layer_set_active_for_tracks (layer, active, tracks)) {
+    GST_VALIDATE_REPORT_ACTION (scenario, action,
+        SCENARIO_ACTION_EXECUTION_ERROR,
+        "Could not set active for track defined in %" GST_PTR_FORMAT,
+        action->structure);
+    res = GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
+    goto done;
+  }
+
+done:
+  g_strfreev (track_names);
+  gst_object_unref (timeline);
+  g_list_free_full (tracks, gst_object_unref);
+
+  return res;
+}
+
 #endif
 
 gboolean
@@ -1404,6 +1472,43 @@ ges_validate_register_action_types (void)
         {NULL}
       }, "Allows to change child property of an object", GST_VALIDATE_ACTION_TYPE_NONE);
 
+  gst_validate_register_action_type ("set-layer-active", "ges", set_layer_active,
+      (GstValidateActionParameter []) {
+        {
+          .name = "layer-priority",
+          .description = "The priority of the layer to set activness on",
+          .types = "gint",
+          .mandatory = TRUE,
+        },
+        {
+          .name = "active",
+          .description = "The activness of the layer",
+          .types = "gboolean",
+          .mandatory = TRUE,
+        },
+        {
+          .name = "tracks",
+          .description = "tracks",
+          .types = "{string, }",
+          .mandatory = FALSE,
+        },
+        {NULL}
+      }, "Set activness of a layer (on optional tracks).",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
+  gst_validate_register_action_type ("set-ges-properties", "ges", set_or_check_properties,
+      (GstValidateActionParameter []) {
+        {
+          .name = "element-name",
+          .description = "The name of the element on which to set properties",
+          .types = "string",
+          .mandatory = TRUE,
+        },
+        {NULL}
+      }, "Set `element-name` properties values defined by the"
+         " fields in the following format: `property_name=expected-value`",
+        GST_VALIDATE_ACTION_TYPE_NONE);
+
   gst_validate_register_action_type ("check-ges-properties", "ges", set_or_check_properties,
       (GstValidateActionParameter []) {
         {
index c591a065d0d3a71c8d340697923b0eaa7d37e94d..9cb844cff91eea439c016c0f72b5710b8e738d5e 100644 (file)
@@ -35,8 +35,8 @@
 
 #define parent_class ges_xml_formatter_parent_class
 #define API_VERSION 0
-#define MINOR_VERSION 6
-#define VERSION 0.5
+#define MINOR_VERSION 7
+#define VERSION 0.7
 
 #define COLLECT_STR_OPT (G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL)
 
@@ -471,13 +471,16 @@ _parse_layer (GMarkupParseContext * context, const gchar * element_name,
   guint priority;
   GType extractable_type = G_TYPE_NONE;
   const gchar *metadatas = NULL, *properties = NULL, *strprio = NULL,
-      *extractable_type_name;
+      *extractable_type_name, *deactivated_tracks_str;
+
+  gchar **deactivated_tracks = NULL;
 
   if (!g_markup_collect_attributes (element_name, attribute_names,
           attribute_values, error,
           G_MARKUP_COLLECT_STRING, "priority", &strprio,
           COLLECT_STR_OPT, "extractable-type-name", &extractable_type_name,
           COLLECT_STR_OPT, "properties", &properties,
+          COLLECT_STR_OPT, "deactivated-tracks", &deactivated_tracks_str,
           COLLECT_STR_OPT, "metadatas", &metadatas, G_MARKUP_COLLECT_INVALID))
     return;
 
@@ -511,8 +514,13 @@ _parse_layer (GMarkupParseContext * context, const gchar * element_name,
   if (errno)
     goto convertion_failed;
 
+  if (deactivated_tracks_str)
+    deactivated_tracks = g_strsplit (deactivated_tracks_str, " ", -1);
+
   ges_base_xml_formatter_add_layer (GES_BASE_XML_FORMATTER (self),
-      extractable_type, priority, props, metadatas, error);
+      extractable_type, priority, props, metadatas, deactivated_tracks, error);
+
+  g_strfreev (deactivated_tracks);
 
 done:
   if (props)
@@ -1084,7 +1092,8 @@ _init_value_from_spec_for_serialization (GValue * value, GParamSpec * spec)
 }
 
 static gchar *
-_serialize_properties (GObject * object, const gchar * fieldname, ...)
+_serialize_properties (GObject * object, gint * ret_n_props,
+    const gchar * fieldname, ...)
 {
   gchar *ret;
   guint n_props, j;
@@ -1097,22 +1106,31 @@ _serialize_properties (GObject * object, const gchar * fieldname, ...)
     GValue val = { 0 };
 
     spec = pspecs[j];
+    if (!_can_serialize_spec (spec))
+      continue;
+
+    _init_value_from_spec_for_serialization (&val, spec);
+    g_object_get_property (object, spec->name, &val);
+    if (gst_value_compare (g_param_spec_get_default_value (spec),
+            &val) == GST_VALUE_EQUAL) {
+      GST_INFO ("Ignoring %s as it is using the default value", spec->name);
+      goto next;
+    }
+
     if (spec->value_type == GST_TYPE_CAPS) {
-      GstCaps *caps;
       gchar *caps_str;
+      const GstCaps *caps = gst_value_get_caps (&val);
 
-      g_object_get (object, spec->name, &caps, NULL);
       caps_str = gst_caps_to_string (caps);
       gst_structure_set (structure, spec->name, G_TYPE_STRING, caps_str, NULL);
-      if (caps)
-        gst_caps_unref (caps);
       g_free (caps_str);
-    } else if (_can_serialize_spec (spec)) {
-      _init_value_from_spec_for_serialization (&val, spec);
-      g_object_get_property (object, spec->name, &val);
-      gst_structure_set_value (structure, spec->name, &val);
-      g_value_unset (&val);
+      goto next;
     }
+
+    gst_structure_set_value (structure, spec->name, &val);
+
+  next:
+    g_value_unset (&val);
   }
   g_free (pspecs);
 
@@ -1124,6 +1142,8 @@ _serialize_properties (GObject * object, const gchar * fieldname, ...)
   }
 
   ret = gst_structure_to_string (structure);
+  if (ret_n_props)
+    *ret_n_props = gst_structure_n_fields (structure);
   gst_structure_free (structure);
 
   return ret;
@@ -1190,7 +1210,7 @@ _save_subproject (GESXmlFormatter * self, GString * str, GESProject * project,
 
   subproject = ges_extractable_get_asset (GES_EXTRACTABLE (timeline));
   substr = g_string_new (NULL);
-  properties = _serialize_properties (G_OBJECT (subproject), NULL);
+  properties = _serialize_properties (G_OBJECT (subproject), NULL, NULL);
   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (subproject));
   append_escaped (str,
       g_markup_printf_escaped
@@ -1248,7 +1268,7 @@ _serialize_streams (GESXmlFormatter * self, GString * str,
         ges_uri_source_asset_get_stream_info (tmp->data);
     GstCaps *caps = gst_discoverer_stream_info_get_caps (sinfo);
 
-    properties = _serialize_properties (tmp->data, NULL);
+    properties = _serialize_properties (tmp->data, NULL, NULL);
     metas = ges_meta_container_metas_to_string (tmp->data);
     capsstr = gst_caps_to_string (caps);
 
@@ -1299,7 +1319,7 @@ _save_assets (GESXmlFormatter * self, GString * str, GESProject * project,
       G_UNLOCK (uri_subprojects_map_lock);
     }
 
-    properties = _serialize_properties (G_OBJECT (asset), NULL);
+    properties = _serialize_properties (G_OBJECT (asset), NULL, NULL);
     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (asset));
     append_escaped (str,
         g_markup_printf_escaped
@@ -1363,7 +1383,7 @@ _save_tracks (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
   tracks = ges_timeline_get_tracks (timeline);
   for (tmp = tracks; tmp; tmp = tmp->next) {
     track = GES_TRACK (tmp->data);
-    properties = _serialize_properties (G_OBJECT (track), "caps", NULL);
+    properties = _serialize_properties (G_OBJECT (track), NULL, "caps", NULL);
     strtmp = gst_caps_to_string (ges_track_get_caps (track));
     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (track));
     append_escaped (str,
@@ -1512,7 +1532,7 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
   }
   g_list_free_full (tracks, gst_object_unref);
 
-  properties = _serialize_properties (G_OBJECT (trackelement), "start",
+  properties = _serialize_properties (G_OBJECT (trackelement), NULL, "start",
       "in-point", "duration", "locked", "max-duration", "name", "priority",
       NULL);
   metas =
@@ -1537,6 +1557,79 @@ _save_effect (GString * str, guint clip_id, GESTrackElement * trackelement,
       depth);
 }
 
+static inline void
+_save_layer_track_activness (GESXmlFormatter * self, GESLayer * layer,
+    GString * str, GESTimeline * timeline, guint depth)
+{
+  guint nb_tracks = 0, i;
+  GList *tmp, *tracks = ges_timeline_get_tracks (timeline);
+  GArray *deactivated_tracks = g_array_new (TRUE, FALSE, sizeof (gint32));
+
+  for (tmp = tracks; tmp; tmp = tmp->next, nb_tracks++) {
+    if (!ges_layer_get_active_for_track (layer, tmp->data))
+      g_array_append_val (deactivated_tracks, nb_tracks);
+  }
+
+  if (!deactivated_tracks->len) {
+    g_string_append (str, ">\n");
+    goto done;
+  }
+
+  self->priv->min_version = MAX (self->priv->min_version, 7);
+  g_string_append (str, " deactivated-tracks='");
+  for (i = 0; i < deactivated_tracks->len; i++)
+    g_string_append_printf (str, "%d ", g_array_index (deactivated_tracks, gint,
+            i));
+  g_string_append (str, "'>\n");
+
+done:
+  g_array_free (deactivated_tracks, TRUE);
+  g_list_free_full (tracks, gst_object_unref);
+}
+
+static void
+_save_source (GESXmlFormatter * self, GString * str,
+    GESTimelineElement * element, GESTimeline * timeline, GList * tracks,
+    guint depth)
+{
+  gint index, n_props;
+  gboolean serialize;
+  gchar *properties;
+
+  if (!GES_IS_SOURCE (element))
+    return;
+
+  g_object_get (element, "serialize", &serialize, NULL);
+  if (!serialize) {
+    GST_DEBUG_OBJECT (element, "Should not be serialized");
+    return;
+  }
+
+  index =
+      g_list_index (tracks,
+      ges_track_element_get_track (GES_TRACK_ELEMENT (element)));
+  append_escaped (str,
+      g_markup_printf_escaped
+      ("          <source track-id='%i' ", index), depth);
+
+  properties = _serialize_properties (G_OBJECT (element), &n_props,
+      "in-point", "priority", "start", "duration", "track", "track-type"
+      "uri", "name", "max-duration", NULL);
+
+  /* Try as possible to allow older versions of GES to load the files */
+  if (n_props) {
+    self->priv->min_version = MAX (self->priv->min_version, 7);
+    g_string_append_printf (str, "properties='%s' ", properties);
+  }
+  g_free (properties);
+
+  _save_children_properties (str, element, depth);
+  append_escaped (str, g_markup_printf_escaped (">\n"), depth);
+  _save_keyframes (str, GES_TRACK_ELEMENT (element), index, depth);
+  append_escaped (str, g_markup_printf_escaped ("          </source>\n"),
+      depth);
+}
+
 static inline void
 _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
     guint depth)
@@ -1552,15 +1645,18 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
     layer = GES_LAYER (tmplayer->data);
 
     priority = ges_layer_get_priority (layer);
-    properties = _serialize_properties (G_OBJECT (layer), "priority", NULL);
+    properties =
+        _serialize_properties (G_OBJECT (layer), NULL, "priority", NULL);
     metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (layer));
     append_escaped (str,
         g_markup_printf_escaped
-        ("      <layer priority='%i' properties='%s' metadatas='%s'>\n",
+        ("      <layer priority='%i' properties='%s' metadatas='%s'",
             priority, properties, metas), depth);
     g_free (properties);
     g_free (metas);
 
+    _save_layer_track_activness (self, layer, str, timeline, depth);
+
     clips = ges_layer_get_clips (layer);
     for (tmpclip = clips; tmpclip; tmpclip = tmpclip->next) {
       GList *effects, *tmpeffect;
@@ -1579,7 +1675,7 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
 
       /* We escape all mandatrorry properties that are handled sparetely
        * and vtype for StandarTransition as it is the asset ID */
-      properties = _serialize_properties (G_OBJECT (clip),
+      properties = _serialize_properties (G_OBJECT (clip), NULL,
           "supported-formats", "rate", "in-point", "start", "duration",
           "max-duration", "priority", "vtype", "uri", NULL);
       extractable_id = ges_extractable_get_id (GES_EXTRACTABLE (clip));
@@ -1629,31 +1725,9 @@ _save_layers (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
 
       for (tmptrackelement = GES_CONTAINER_CHILDREN (clip); tmptrackelement;
           tmptrackelement = tmptrackelement->next) {
-        gint index;
-        gboolean serialize;
-
-        if (!GES_IS_SOURCE (tmptrackelement->data))
-          continue;
-
-        g_object_get (tmptrackelement->data, "serialize", &serialize, NULL);
-        if (!serialize) {
-          GST_DEBUG_OBJECT (tmptrackelement->data, "Should not be serialized");
-          continue;
-        }
-
-        index =
-            g_list_index (tracks,
-            ges_track_element_get_track (tmptrackelement->data));
-        append_escaped (str,
-            g_markup_printf_escaped ("          <source track-id='%i'", index),
-            depth);
-        _save_children_properties (str, tmptrackelement->data, depth);
-        append_escaped (str, g_markup_printf_escaped (">\n"), depth);
-        _save_keyframes (str, tmptrackelement->data, index, depth);
-        append_escaped (str, g_markup_printf_escaped ("          </source>\n"),
+        _save_source (self, str, tmptrackelement->data, timeline, tracks,
             depth);
       }
-
       g_list_free_full (tracks, gst_object_unref);
 
       string_append_with_depth (str, "        </clip>\n", depth);
@@ -1695,7 +1769,7 @@ _save_group (GESXmlFormatter * self, GString * str, GList ** seen_groups,
     }
   }
 
-  properties = _serialize_properties (G_OBJECT (group), NULL);
+  properties = _serialize_properties (G_OBJECT (group), NULL, NULL);
 
   metadatas = ges_meta_container_metas_to_string (GES_META_CONTAINER (group));
   self->priv->min_version = MAX (self->priv->min_version, 5);
@@ -1742,7 +1816,8 @@ _save_timeline (GESXmlFormatter * self, GString * str, GESTimeline * timeline,
 {
   gchar *properties = NULL, *metas = NULL;
 
-  properties = _serialize_properties (G_OBJECT (timeline), "update", "name",
+  properties =
+      _serialize_properties (G_OBJECT (timeline), NULL, "update", "name",
       "async-handling", "message-forward", NULL);
 
   ges_meta_container_set_uint64 (GES_META_CONTAINER (timeline), "duration",
@@ -1815,10 +1890,10 @@ _save_stream_profiles (GESXmlFormatter * self, GString * str,
       if (GST_IS_PRESET (encoder) &&
           gst_preset_load_preset (GST_PRESET (encoder), preset)) {
 
-        gchar *settings = _serialize_properties (G_OBJECT (encoder), NULL);
-        append_escaped (str,
-            g_markup_printf_escaped ("preset-properties='%s' ", settings),
-            depth);
+        gchar *settings =
+            _serialize_properties (G_OBJECT (encoder), NULL, NULL);
+        append_escaped (str, g_markup_printf_escaped ("preset-properties='%s' ",
+                settings), depth);
         g_free (settings);
       }
       gst_object_unref (encoder);
@@ -1893,7 +1968,8 @@ _save_encoding_profiles (GESXmlFormatter * self, GString * str,
       if (element) {
         if (GST_IS_PRESET (element) &&
             gst_preset_load_preset (GST_PRESET (element), profpreset)) {
-          gchar *settings = _serialize_properties (G_OBJECT (element), NULL);
+          gchar *settings =
+              _serialize_properties (G_OBJECT (element), NULL, NULL);
           append_escaped (str,
               g_markup_printf_escaped ("preset-properties='%s' ", settings),
               depth);
@@ -1960,7 +2036,7 @@ _save_project (GESFormatter * formatter, GString * str, GESProject * project,
   GESXmlFormatter *self = GES_XML_FORMATTER (formatter);
   GESXmlFormatterPrivate *priv = _GET_PRIV (formatter);
 
-  properties = _serialize_properties (G_OBJECT (project), NULL);
+  properties = _serialize_properties (G_OBJECT (project), NULL, NULL);
   metas = ges_meta_container_metas_to_string (GES_META_CONTAINER (project));
   append_escaped (str,
       g_markup_printf_escaped ("  <project properties='%s' metadatas='%s'>\n",
index fe25a637377eb50808bb88e540b5d01326c485da..98d9bc6d70d871212c4d9dd6233ac2f501f347dd 100644 (file)
@@ -77,6 +77,7 @@ if gstvalidate_dep.found()
     'check_video_track_restriction_scale_with_keyframes',
     'check_edit_in_frames',
     'check_edit_in_frames_with_framerate_mismatch',
+    'check_layer_activness_gaps',
   ]
 
   env = environment()
index 0f55c929e62c3d72d9ef3fe7a31444a1d8012ee1..56da4db513c3265292362ea25a022cadd4b1d367 100644 (file)
@@ -26,6 +26,7 @@ gi.require_version("GES", "1.0")
 from gi.repository import Gst  # noqa
 from gi.repository import GES  # noqa
 from gi.repository import GLib  # noqa
+from gi.repository import GObject  # noqa
 import contextlib  # noqa
 import os  #noqa
 import unittest  # noqa
@@ -208,6 +209,93 @@ class GESSimpleTimelineTest(GESTest):
 
         return clip
 
+    def assertElementAreEqual(self, ref, element):
+        self.assertTrue(isinstance(element, type(ref)), "%s and %s do not have the same type!" % (ref, element))
+
+        props = [p for p in ref.list_properties() if p.name not in ['name']
+            and not GObject.type_is_a(p.value_type, GObject.Object)]
+        for p in props:
+            pname = p.name
+            v0 = GObject.Value()
+            v0.init(p.value_type)
+            v0.set_value(ref.get_property(pname))
+
+            v1 = GObject.Value()
+            v1.init(p.value_type)
+            v1.set_value(element.get_property(pname))
+
+            self.assertTrue(Gst.value_compare(v0, v1) == Gst.VALUE_EQUAL,
+                "%s are not equal: %s != %s" % (pname, v0, v1))
+
+        if isinstance(ref, GES.TrackElement):
+            self.assertElementAreEqual(ref.get_nleobject(), element.get_nleobject())
+            return
+
+        if not isinstance(ref, GES.Clip):
+            return
+
+        ttypes = [track.type for track in self.timeline.get_tracks()]
+        for ttype in ttypes:
+            if ttypes.count(ttype) > 1:
+                self.warning("Can't deeply check %s and %s "
+                    "(only one track per type supported %s %s found)" % (ref,
+                    element, ttypes.count(ttype), ttype))
+                return
+
+        children = element.get_children(False)
+        for ref_child in ref.get_children(False):
+            ref_track = ref_child.get_track()
+            if not ref_track:
+                self.warning("Can't check %s as not in a track" % (ref_child))
+                continue
+
+            child = None
+            for tmpchild in children:
+                if not isinstance(tmpchild, type(ref_child)):
+                    continue
+
+                if ref_track.type != tmpchild.get_track().type:
+                    continue
+
+                if not isinstance(ref_child, GES.Effect):
+                    child = tmpchild
+                    break
+                elif ref_child.props.bin_description == child.props.bin_description:
+                    child = tmpchild
+                    break
+
+            self.assertIsNotNone(child, "Could not find equivalent child %s in %s(%s)" % (ref_child,
+                element, children))
+
+            self.assertElementAreEqual(ref_child, child)
+
+    def check_reload_timeline(self):
+        tmpf = tempfile.NamedTemporaryFile(suffix='.xges')
+        uri = Gst.filename_to_uri(tmpf.name)
+        self.assertTrue(self.timeline.save_to_uri(uri, None, True))
+        project = GES.Project.new(uri)
+        mainloop = create_main_loop()
+        def loaded_cb(unused_project, unused_timeline):
+            mainloop.quit()
+
+        project.connect("loaded", loaded_cb)
+        reloaded_timeline = project.extract()
+
+        mainloop.run()
+        self.assertIsNotNone(reloaded_timeline)
+
+        layers = self.timeline.get_layers()
+        reloaded_layers = reloaded_timeline.get_layers()
+        self.assertEqual(len(layers), len(reloaded_layers))
+        for layer, reloaded_layer in zip(layers, reloaded_layers):
+            clips = layer.get_clips()
+            reloaded_clips = reloaded_layer.get_clips()
+            self.assertEqual(len(clips), len(reloaded_clips))
+            for clip, reloaded_clip in zip(clips, reloaded_clips):
+                self.assertElementAreEqual(clip, reloaded_clip)
+
+        return reloaded_timeline
+
     def assertTimelineTopology(self, topology, groups=[]):
         res = []
         for layer in self.timeline.get_layers():
index 818f0b052b2e282cbdf14f67c8d5a75ba7b16333..22cee6418a8f17718d444eddcc4a2fceaba21eae 100644 (file)
@@ -222,6 +222,107 @@ class TestTimeline(common.GESSimpleTimelineTest):
         self.assertEqual(self.timeline.get_frame_at(Gst.SECOND), 60)
         self.assertEqual(clip.props.max_duration, Gst.SECOND)
 
+    def test_layer_active(self):
+        def check_nle_object_activeness(clip, track_type, active=None, ref_clip=None):
+            assert ref_clip is not None or active is not None
+
+            if ref_clip:
+                ref_elem, = ref_clip.find_track_elements(None, track_type, GES.Source)
+                active = ref_elem.get_nleobject().props.active
+
+            elem, = clip.find_track_elements(None, track_type, GES.Source)
+            self.assertIsNotNone(elem)
+            self.assertEqual(elem.get_nleobject().props.active, active)
+
+        def get_tracks(timeline):
+            for track in self.timeline.get_tracks():
+                if track.props.track_type == GES.TrackType.VIDEO:
+                    video_track = track
+                else:
+                    audio_track = track
+            return video_track, audio_track
+
+
+        def check_set_active_for_tracks(layer, active, tracks, expected_changed_tracks):
+            callback_called = []
+            def _check_active_changed_cb(layer, active, tracks, expected_tracks, expected_active):
+                self.assertEqual(set(tracks), set(expected_tracks))
+                self.assertEqual(active, expected_active)
+                callback_called.append(True)
+
+            layer.connect("active-changed", _check_active_changed_cb, expected_changed_tracks, active)
+            self.assertTrue(layer.set_active_for_tracks(active, tracks))
+            self.layer.disconnect_by_func(_check_active_changed_cb)
+            self.assertEqual(callback_called, [True])
+
+        c0 = self.append_clip()
+        check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c0, GES.TrackType.AUDIO, True)
+
+        elem, = c0.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+        elem.props.active = False
+        check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c0, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+        elem.props.active = True
+
+        # Muting audio track
+        video_track, audio_track = get_tracks(self.timeline)
+
+        check_set_active_for_tracks(self.layer, False, [audio_track], [audio_track])
+
+        check_nle_object_activeness(c0, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c0, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
+        c1 = self.append_clip()
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+        l1 = self.timeline.append_layer()
+        c1.move_to_layer(l1)
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        self.assertTrue(c1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL,
+                   GES.Edge.EDGE_NONE, c1.props.start))
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
+        self.assertTrue(self.layer.remove_clip(c1))
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        self.assertTrue(self.layer.add_clip(c1))
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+        check_set_active_for_tracks(self.layer, True, None, [audio_track])
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        elem, = c1.find_track_elements(None, GES.TrackType.AUDIO, GES.Source)
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, True)
+
+        # Force deactivating a specific TrackElement
+        elem.props.active = False
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, True)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
+        # Try activating a specific TrackElement, that won't change the
+        # underlying nleobject activness
+        check_set_active_for_tracks(self.layer, False, None, [audio_track, video_track])
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, False)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+
+        elem.props.active = True
+        check_nle_object_activeness(c1, GES.TrackType.VIDEO, False)
+        check_nle_object_activeness(c1, GES.TrackType.AUDIO, False)
+        self.check_reload_timeline()
+
 
 class TestEditing(common.GESSimpleTimelineTest):
 
diff --git a/tests/check/scenarios/check_layer_activness_gaps.scenario b/tests/check/scenarios/check_layer_activness_gaps.scenario
new file mode 100644 (file)
index 0000000..05019de
--- /dev/null
@@ -0,0 +1,24 @@
+description, handles-states=true,
+    ges-options={\
+        "--disable-mixing",
+        "--videosink=fakevideosink",
+        "--audiosink=fakesink"\
+    }
+
+add-clip, name=clip, asset-id="framerate=30/1", layer-priority=0, type=GESTestClip, pattern=blue, duration=5000.0
+set-layer-active, tracks={gesvideotrack0}, active=false, layer-priority=0
+
+pause;
+
+# Make sure the video test src is a gap test src.
+check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="100% Black"
+check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Sine"
+
+set-layer-active, tracks={gesvideotrack0}, active=true, layer-priority=0
+set-layer-active, tracks={gesaudiotrack0}, active=false, layer-priority=0
+commit;
+# Make sure the video test src is the GESVideoTestSource and the audio test source is a gap
+check-property, target-element-factory-name=videotestsrc, property-name=pattern, property-value="Blue"
+check-property, target-element-factory-name=audiotestsrc, property-name=wave, property-value="Silence"
+
+stop;
\ No newline at end of file