hlssink: Add segment target-duration property

This property causes hlssink to push a force key unit event upstream
every target-duration seconds. This is for simple automatic segmenting.
To disable it, set the property to 0 and push the force key unit event
manually.
diff --git a/gst/hls/gsthlssink.c b/gst/hls/gsthlssink.c
index 8a67f40..7c5dbe7 100644
--- a/gst/hls/gsthlssink.c
+++ b/gst/hls/gsthlssink.c
@@ -36,6 +36,7 @@
 #define DEFAULT_PLAYLIST_LOCATION "playlist.m3u8"
 #define DEFAULT_PLAYLIST_ROOT NULL
 #define DEFAULT_MAX_FILES 10
+#define DEFAULT_TARGET_DURATION 15
 
 enum
 {
@@ -43,7 +44,8 @@
   PROP_LOCATION,
   PROP_PLAYLIST_LOCATION,
   PROP_PLAYLIST_ROOT,
-  PROP_MAX_FILES
+  PROP_MAX_FILES,
+  PROP_TARGET_DURATION
 };
 
 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
@@ -140,6 +142,13 @@
           "old files start to be deleted to make room for new ones.",
           0, G_MAXUINT, DEFAULT_MAX_FILES,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_TARGET_DURATION,
+      g_param_spec_uint ("target-duration", "Target duration",
+          "The target duration in seconds of a segment/file. "
+          "(0 - disabled, useful for management of segment duration by the "
+          "streaming server)",
+          0, G_MAXUINT, DEFAULT_TARGET_DURATION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -160,6 +169,9 @@
   sink->playlist_root = g_strdup (DEFAULT_PLAYLIST_ROOT);
   sink->playlist = gst_m3u8_playlist_new (6, 5, FALSE);
   sink->max_files = DEFAULT_MAX_FILES;
+  sink->target_duration = DEFAULT_TARGET_DURATION;
+  sink->count = 0;
+  sink->timeout_id = 0;
 }
 
 static gboolean
@@ -250,6 +262,27 @@
   GST_BIN_CLASS (parent_class)->handle_message (bin, message);
 }
 
+static gboolean
+send_force_key_unit_event (gpointer user_data)
+{
+  GstHlsSink *sink = GST_HLS_SINK_CAST (user_data);
+  GstPad *sinkpad = gst_element_get_static_pad (GST_ELEMENT (sink), "sink");
+
+  /* FIXME - try to make segments >= target duration in length by setting the
+   * time parameter of the force key unit event to last segment cut time +
+   * target duration */
+  sink->count++;
+  if (!gst_pad_push_event (sinkpad,
+          gst_video_event_new_upstream_force_key_unit (sink->target_duration *
+              sink->count, TRUE, sink->count))) {
+    GST_WARNING_OBJECT (sink, "Failed to push upstream force key unit event");
+  }
+
+  gst_object_unref (sinkpad);
+
+  return TRUE;
+}
+
 static GstStateChangeReturn
 gst_hls_sink_change_state (GstElement * element, GstStateChange trans)
 {
@@ -262,6 +295,13 @@
         return GST_STATE_CHANGE_FAILURE;
       }
       break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      if (sink->target_duration) {
+        sink->timeout_id =
+            g_timeout_add_seconds (sink->target_duration,
+            send_force_key_unit_event, sink);
+      }
+      break;
     default:
       break;
   }
@@ -269,6 +309,16 @@
   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, trans);
 
   switch (trans) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      if (sink->timeout_id && !g_source_remove (sink->timeout_id)) {
+        GST_WARNING_OBJECT (sink, "Failed to remove target-duration timeout");
+      }
+      sink->timeout_id = 0;
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_READY:
+      /* reset the segment/file count */
+      sink->count = 0;
+      break;
     case GST_STATE_CHANGE_READY_TO_NULL:
       break;
     default:
@@ -307,6 +357,9 @@
             NULL);
       }
       break;
+    case PROP_TARGET_DURATION:
+      sink->target_duration = g_value_get_uint (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -332,6 +385,9 @@
     case PROP_MAX_FILES:
       g_value_set_uint (value, sink->max_files);
       break;
+    case PROP_TARGET_DURATION:
+      g_value_set_uint (value, sink->target_duration);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
diff --git a/gst/hls/gsthlssink.h b/gst/hls/gsthlssink.h
index 44c60da..e678e0c 100644
--- a/gst/hls/gsthlssink.h
+++ b/gst/hls/gsthlssink.h
@@ -50,6 +50,9 @@
   GstM3U8Playlist *playlist;
   guint index;
   gint max_files;
+  gint target_duration;
+  gint count;
+  guint timeout_id;
 };
 
 struct _GstHlsSinkClass