downloadbuffer: unlock mutex in error case
[platform/upstream/gstreamer.git] / plugins / elements / gstdownloadbuffer.c
index 9b155fc..36455ac 100644 (file)
@@ -21,6 +21,7 @@
 
 /**
  * SECTION:element-downloadbuffer
+ * @title: downloadbuffer
  *
  * The downloadbuffer element provides on-disk buffering and caching of, typically,
  * a network file. temp-template should be set to a value such as
@@ -30,7 +31,7 @@
  * With max-size-bytes and max-size-time you can configure the buffering limits.
  * The downloadbuffer element will try to read-ahead these amounts of data. When
  * the amount of read-ahead data drops below low-percent of the configured max,
- * the element will start emiting BUFFERING messages until high-percent of max is
+ * the element will start emitting BUFFERING messages until high-percent of max is
  * reached again.
  *
  * The downloadbuffer provides push and pull based scheduling on its source pad
  * When the downloadbuffer has completely downloaded the media, it will
  * post an application message named  <classname>&quot;GstCacheDownloadComplete&quot;</classname>
  * with the following information:
- * <itemizedlist>
- * <listitem>
- *   <para>
+ *
+ * *
  *   G_TYPE_STRING
  *   <classname>&quot;location&quot;</classname>:
  *   the location of the completely downloaded file.
- *   </para>
- * </listitem>
- * </itemizedlist>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
 #include <io.h>                 /* lseek, open, close, read */
 #undef lseek
 #define lseek _lseeki64
-#undef off_t
-#define off_t guint64
 #else
 #include <unistd.h>
 #endif
 
+#ifdef __BIONIC__
+#include <fcntl.h>
+#endif
+
 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
@@ -174,7 +174,7 @@ enum
 G_DEFINE_TYPE_WITH_CODE (GstDownloadBuffer, gst_download_buffer,
     GST_TYPE_ELEMENT, _do_init);
 
-static void update_buffering (GstDownloadBuffer * dlbuf);
+static GstMessage *update_buffering (GstDownloadBuffer * dlbuf);
 
 static void gst_download_buffer_finalize (GObject * object);
 
@@ -225,11 +225,13 @@ gst_download_buffer_class_init (GstDownloadBufferClass * klass)
       g_param_spec_uint ("max-size-bytes", "Max. size (kB)",
           "Max. amount of data to buffer (bytes, 0=disable)",
           0, G_MAXUINT, DEFAULT_MAX_SIZE_BYTES,
-          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
+          G_PARAM_STATIC_STRINGS));
   g_object_class_install_property (gobject_class, PROP_MAX_SIZE_TIME,
       g_param_spec_uint64 ("max-size-time", "Max. size (ns)",
           "Max. amount of data to buffer (in ns, 0=disable)", 0, G_MAXUINT64,
-          DEFAULT_MAX_SIZE_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+          DEFAULT_MAX_SIZE_TIME, G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
+          G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class, PROP_LOW_PERCENT,
       g_param_spec_int ("low-percent", "Low percent",
@@ -267,10 +269,8 @@ gst_download_buffer_class_init (GstDownloadBufferClass * klass)
   /* set several parent class virtual functions */
   gobject_class->finalize = gst_download_buffer_finalize;
 
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&srctemplate));
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&sinktemplate));
+  gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
+  gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
 
   gst_element_class_set_static_metadata (gstelement_class, "DownloadBuffer",
       "Generic", "Download Buffer element",
@@ -327,8 +327,6 @@ gst_download_buffer_init (GstDownloadBuffer * dlbuf)
   dlbuf->waiting_add = FALSE;
   g_cond_init (&dlbuf->item_add);
 
-  dlbuf->buffering_percent = 100;
-
   /* tempfile related */
   dlbuf->temp_template = NULL;
   dlbuf->temp_location = NULL;
@@ -354,6 +352,18 @@ gst_download_buffer_finalize (GObject * object)
 }
 
 static void
+reset_positions (GstDownloadBuffer * dlbuf)
+{
+  dlbuf->write_pos = 0;
+  dlbuf->read_pos = 0;
+  dlbuf->filling = TRUE;
+  dlbuf->buffering_percent = 0;
+  dlbuf->is_buffering = TRUE;
+  dlbuf->seeking = FALSE;
+  GST_DOWNLOAD_BUFFER_CLEAR_LEVEL (dlbuf->cur_level);
+}
+
+static void
 reset_rate_timer (GstDownloadBuffer * dlbuf)
 {
   dlbuf->bytes_in = 0;
@@ -380,25 +390,19 @@ reset_rate_timer (GstDownloadBuffer * dlbuf)
 #define AVG_OUT(avg,val) ((avg) * 3.0 + (val)) / 4.0
 
 static void
-update_time_level (GstDownloadBuffer * dlbuf)
+update_levels (GstDownloadBuffer * dlbuf, guint bytes)
 {
+  dlbuf->cur_level.bytes = bytes;
+
   if (dlbuf->byte_in_rate > 0.0) {
     dlbuf->cur_level.time =
         dlbuf->cur_level.bytes / dlbuf->byte_in_rate * GST_SECOND;
   }
+
   GST_DEBUG ("levels: bytes %u/%u, time %" GST_TIME_FORMAT "/%" GST_TIME_FORMAT,
       dlbuf->cur_level.bytes, dlbuf->max_level.bytes,
       GST_TIME_ARGS (dlbuf->cur_level.time),
       GST_TIME_ARGS (dlbuf->max_level.time));
-  /* update the buffering */
-  update_buffering (dlbuf);
-}
-
-static void
-update_levels (GstDownloadBuffer * dlbuf, guint bytes)
-{
-  dlbuf->cur_level.bytes = bytes;
-  update_time_level (dlbuf);
 }
 
 static void
@@ -556,14 +560,15 @@ get_buffering_stats (GstDownloadBuffer * dlbuf, gint percent,
   }
 }
 
-static void
+static GstMessage *
 update_buffering (GstDownloadBuffer * dlbuf)
 {
   gint percent;
   gboolean post = FALSE;
+  GstMessage *message = NULL;
 
   if (!get_buffering_percent (dlbuf, NULL, &percent))
-    return;
+    return NULL;
 
   if (dlbuf->is_buffering) {
     post = TRUE;
@@ -587,7 +592,6 @@ update_buffering (GstDownloadBuffer * dlbuf)
   }
 
   if (post) {
-    GstMessage *message;
     GstBufferingMode mode;
     gint avg_in, avg_out;
     gint64 buffering_left;
@@ -599,9 +603,9 @@ update_buffering (GstDownloadBuffer * dlbuf)
         (gint) percent);
     gst_message_set_buffering_stats (message, mode,
         avg_in, avg_out, buffering_left);
-
-    gst_element_post_message (GST_ELEMENT_CAST (dlbuf), message);
   }
+
+  return message;
 }
 
 static gboolean
@@ -644,6 +648,7 @@ get_seek_threshold (GstDownloadBuffer * dlbuf)
   return threshold;
 }
 
+/* called with DOWNLOAD_BUFFER_MUTEX */
 static void
 gst_download_buffer_update_upstream_size (GstDownloadBuffer * dlbuf)
 {
@@ -726,6 +731,7 @@ out_flushing:
   }
 }
 
+/* called with DOWNLOAD_BUFFER_MUTEX */
 static gboolean
 check_upstream_size (GstDownloadBuffer * dlbuf, gsize offset, guint * length)
 {
@@ -832,6 +838,7 @@ hit_eos:
 out_flushing:
   {
     GST_DEBUG_OBJECT (dlbuf, "we are flushing");
+    g_clear_error (&error);
     gst_buffer_unmap (buf, &info);
     if (*buffer == NULL)
       gst_buffer_unref (buf);
@@ -861,7 +868,7 @@ gst_download_buffer_open_temp_location_file (GstDownloadBuffer * dlbuf)
 
   GST_DEBUG_OBJECT (dlbuf, "opening temp file %s", dlbuf->temp_template);
 
-  /* If temp_template was set, allocate a filename and open that filen */
+  /* If temp_template was set, allocate a filename and open that file */
 
   /* nothing to do */
   if (dlbuf->temp_template == NULL)
@@ -869,7 +876,11 @@ gst_download_buffer_open_temp_location_file (GstDownloadBuffer * dlbuf)
 
   /* make copy of the template, we don't want to change this */
   name = g_strdup (dlbuf->temp_template);
+#ifdef __BIONIC__
+  fd = g_mkstemp_full (name, O_RDWR | O_LARGEFILE, S_IRUSR | S_IWUSR);
+#else
   fd = g_mkstemp (name);
+#endif
   if (fd == -1)
     goto mkstemp_failed;
 
@@ -882,6 +893,7 @@ gst_download_buffer_open_temp_location_file (GstDownloadBuffer * dlbuf)
   g_free (dlbuf->temp_location);
   dlbuf->temp_location = name;
   dlbuf->temp_fd = fd;
+  reset_positions (dlbuf);
 
   GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
 
@@ -962,7 +974,7 @@ gst_download_buffer_locked_flush (GstDownloadBuffer * dlbuf, gboolean full,
 {
   if (clear_temp)
     gst_download_buffer_flush_temp_file (dlbuf);
-  GST_DOWNLOAD_BUFFER_CLEAR_LEVEL (dlbuf->cur_level);
+  reset_positions (dlbuf);
   gst_event_replace (&dlbuf->stream_start_event, NULL);
   gst_event_replace (&dlbuf->segment_event, NULL);
 }
@@ -1038,6 +1050,8 @@ gst_download_buffer_handle_sink_event (GstPad * pad, GstObject * parent,
     }
     default:
       if (GST_EVENT_IS_SERIALIZED (event)) {
+        GstMessage *msg = NULL;
+
         /* serialized events go in the buffer */
         GST_DOWNLOAD_BUFFER_MUTEX_LOCK_CHECK (dlbuf, dlbuf->sinkresult,
             out_flushing);
@@ -1048,6 +1062,8 @@ gst_download_buffer_handle_sink_event (GstPad * pad, GstObject * parent,
              * filled and we can read all data from the dlbuf. */
             /* update the buffering status */
             update_levels (dlbuf, dlbuf->max_level.bytes);
+            /* update the buffering */
+            msg = update_buffering (dlbuf);
             /* wakeup the waiter and let it recheck */
             GST_DOWNLOAD_BUFFER_SIGNAL_ADD (dlbuf, -1);
             break;
@@ -1065,6 +1081,8 @@ gst_download_buffer_handle_sink_event (GstPad * pad, GstObject * parent,
         }
         gst_event_unref (event);
         GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
+        if (msg != NULL)
+          gst_element_post_message (GST_ELEMENT_CAST (dlbuf), msg);
       } else {
         /* non-serialized events are passed upstream. */
         ret = gst_pad_push_event (dlbuf->srcpad, event);
@@ -1114,6 +1132,7 @@ gst_download_buffer_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
   guint64 offset;
   gsize res, available;
   GError *error = NULL;
+  GstMessage *msg = NULL;
 
   dlbuf = GST_DOWNLOAD_BUFFER (parent);
 
@@ -1195,8 +1214,14 @@ gst_download_buffer_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
       update_levels (dlbuf, 0);
   }
 
+  /* update the buffering */
+  msg = update_buffering (dlbuf);
+
   GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
 
+  if (msg != NULL)
+    gst_element_post_message (GST_ELEMENT_CAST (dlbuf), msg);
+
   return GST_FLOW_OK;
 
   /* ERRORS */
@@ -1225,6 +1250,7 @@ out_seeking:
   }
 write_error:
   {
+    GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
     gst_buffer_unmap (buffer, &info);
     gst_buffer_unref (buffer);
     GST_ELEMENT_ERROR (dlbuf, RESOURCE, WRITE,
@@ -1238,12 +1264,18 @@ completed:
     dlbuf->write_pos = dlbuf->upstream_size;
     dlbuf->filling = FALSE;
     update_levels (dlbuf, dlbuf->max_level.bytes);
+    msg = update_buffering (dlbuf);
+
     gst_element_post_message (GST_ELEMENT_CAST (dlbuf),
         gst_message_new_element (GST_OBJECT_CAST (dlbuf),
             gst_structure_new ("GstCacheDownloadComplete",
                 "location", G_TYPE_STRING, dlbuf->temp_location, NULL)));
+
     GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
 
+    if (msg != NULL)
+      gst_element_post_message (GST_ELEMENT_CAST (dlbuf), msg);
+
     return GST_FLOW_EOS;
   }
 }
@@ -1256,6 +1288,7 @@ gst_download_buffer_loop (GstPad * pad)
   GstDownloadBuffer *dlbuf;
   GstFlowReturn ret;
   GstBuffer *buffer = NULL;
+  GstMessage *msg = NULL;
 
   dlbuf = GST_DOWNLOAD_BUFFER (GST_PAD_PARENT (pad));
 
@@ -1275,9 +1308,15 @@ gst_download_buffer_loop (GstPad * pad)
   if (ret != GST_FLOW_OK)
     goto out_flushing;
 
+  /* update the buffering */
+  msg = update_buffering (dlbuf);
+
   g_atomic_int_set (&dlbuf->downstream_may_block, 1);
   GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
 
+  if (msg != NULL)
+    gst_element_post_message (GST_ELEMENT_CAST (dlbuf), msg);
+
   ret = gst_pad_push (dlbuf->srcpad, buffer);
   g_atomic_int_set (&dlbuf->downstream_may_block, 0);
 
@@ -1305,10 +1344,7 @@ out_flushing:
        * file. */
       gst_pad_push_event (dlbuf->srcpad, gst_event_new_eos ());
     } else if ((ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS)) {
-      GST_ELEMENT_ERROR (dlbuf, STREAM, FAILED,
-          (_("Internal data flow error.")),
-          ("streaming task paused, reason %s (%d)",
-              gst_flow_get_name (ret), ret));
+      GST_ELEMENT_FLOW_ERROR (dlbuf, ret);
       gst_pad_push_event (dlbuf->srcpad, gst_event_new_eos ());
     }
     return;
@@ -1397,9 +1433,13 @@ gst_download_buffer_handle_src_query (GstPad * pad, GstObject * parent,
       switch (format) {
         case GST_FORMAT_BYTES:
           peer_pos -= dlbuf->cur_level.bytes;
+          if (peer_pos < 0)     /* Clamp result to 0 */
+            peer_pos = 0;
           break;
         case GST_FORMAT_TIME:
           peer_pos -= dlbuf->cur_level.time;
+          if (peer_pos < 0)     /* Clamp result to 0 */
+            peer_pos = 0;
           break;
         default:
           GST_WARNING_OBJECT (dlbuf, "dropping query in %s format, don't "
@@ -1447,6 +1487,7 @@ gst_download_buffer_handle_src_query (GstPad * pad, GstObject * parent,
         gint64 duration;
         gsize offset, range_start, range_stop;
 
+        GST_DOWNLOAD_BUFFER_MUTEX_LOCK (dlbuf);
         write_pos = dlbuf->write_pos;
 
         /* get duration of upstream in bytes */
@@ -1456,49 +1497,35 @@ gst_download_buffer_handle_src_query (GstPad * pad, GstObject * parent,
         GST_DEBUG_OBJECT (dlbuf, "percent %d, duration %" G_GINT64_FORMAT
             ", writing %" G_GINT64_FORMAT, percent, duration, write_pos);
 
-        /* calculate remaining and total download time */
-        if (duration > write_pos && avg_in > 0.0)
-          estimated_total = ((duration - write_pos) * 1000) / avg_in;
-        else
-          estimated_total = -1;
-
-        GST_DEBUG_OBJECT (dlbuf, "estimated-total %" G_GINT64_FORMAT,
-            estimated_total);
-
         gst_query_parse_buffering_range (query, &format, NULL, NULL, NULL);
 
-        switch (format) {
-          case GST_FORMAT_PERCENT:
-            start = 0;
-            /* get our available data relative to the duration */
-            if (duration != -1)
-              stop =
-                  gst_util_uint64_scale (GST_FORMAT_PERCENT_MAX, write_pos,
-                  duration);
-            else
-              stop = -1;
-            break;
-          case GST_FORMAT_BYTES:
-            start = 0;
-            stop = write_pos;
-            break;
-          default:
-            start = -1;
-            stop = -1;
-            break;
-        }
-
-        gst_query_set_buffering_range (query, format, start, stop,
-            estimated_total);
-
         /* fill out the buffered ranges */
-        offset = 0;
+        start = offset = 0;
+        stop = -1;
+        estimated_total = -1;
         while (gst_sparse_file_get_range_after (dlbuf->file, offset,
                 &range_start, &range_stop)) {
+          gboolean current_range;
+
+          GST_DEBUG_OBJECT (dlbuf,
+              "range starting at %" G_GSIZE_FORMAT " and finishing at %"
+              G_GSIZE_FORMAT, range_start, range_stop);
+
           offset = range_stop;
 
+          /* find the range we are currently downloading, we'll remember it
+           * after we convert to the target format */
+          if (range_start <= write_pos && range_stop >= write_pos) {
+            current_range = TRUE;
+            /* calculate remaining and total download time */
+            if (duration >= range_stop && avg_in > 0.0)
+              estimated_total = ((duration - range_stop) * 1000) / avg_in;
+          } else
+            current_range = FALSE;
+
           switch (format) {
             case GST_FORMAT_PERCENT:
+              /* get our available data relative to the duration */
               if (duration == -1) {
                 range_start = 0;
                 range_stop = 0;
@@ -1516,13 +1543,27 @@ gst_download_buffer_handle_src_query (GstPad * pad, GstObject * parent,
               range_stop = -1;
               break;
           }
+
+          if (current_range) {
+            /* we are currently downloading this range */
+            start = range_start;
+            stop = range_stop;
+          }
+          GST_DEBUG_OBJECT (dlbuf,
+              "range to format: %" G_GSIZE_FORMAT " - %" G_GSIZE_FORMAT,
+              range_start, range_stop);
           if (range_start == range_stop)
             continue;
-          GST_DEBUG_OBJECT (dlbuf,
-              "range starting at %" G_GSIZE_FORMAT " and finishing at %"
-              G_GSIZE_FORMAT, range_start, range_stop);
           gst_query_add_buffering_range (query, range_start, range_stop);
         }
+
+        GST_DEBUG_OBJECT (dlbuf, "estimated-total %" G_GINT64_FORMAT,
+            estimated_total);
+
+        gst_query_set_buffering_range (query, format, start, stop,
+            estimated_total);
+
+        GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
       }
       break;
     }
@@ -1575,14 +1616,21 @@ gst_download_buffer_get_range (GstPad * pad, GstObject * parent, guint64 offset,
 {
   GstDownloadBuffer *dlbuf;
   GstFlowReturn ret;
+  GstMessage *msg = NULL;
 
   dlbuf = GST_DOWNLOAD_BUFFER_CAST (parent);
 
   GST_DOWNLOAD_BUFFER_MUTEX_LOCK_CHECK (dlbuf, dlbuf->srcresult, out_flushing);
   /* FIXME - function will block when the range is not yet available */
   ret = gst_download_buffer_read_buffer (dlbuf, offset, length, buffer);
+  /* update the buffering */
+  msg = update_buffering (dlbuf);
+
   GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
 
+  if (msg != NULL)
+    gst_element_post_message (GST_ELEMENT_CAST (dlbuf), msg);
+
   return ret;
 
   /* ERRORS */
@@ -1821,6 +1869,7 @@ gst_download_buffer_set_property (GObject * object,
     guint prop_id, const GValue * value, GParamSpec * pspec)
 {
   GstDownloadBuffer *dlbuf = GST_DOWNLOAD_BUFFER (object);
+  GstMessage *msg = NULL;
 
   /* someone could change levels here, and since this
    * affects the get/put funcs, we need to lock for safety. */
@@ -1829,11 +1878,11 @@ gst_download_buffer_set_property (GObject * object,
   switch (prop_id) {
     case PROP_MAX_SIZE_BYTES:
       dlbuf->max_level.bytes = g_value_get_uint (value);
-      CAPACITY_CHANGE (dlbuf);
+      msg = CAPACITY_CHANGE (dlbuf);
       break;
     case PROP_MAX_SIZE_TIME:
       dlbuf->max_level.time = g_value_get_uint64 (value);
-      CAPACITY_CHANGE (dlbuf);
+      msg = CAPACITY_CHANGE (dlbuf);
       break;
     case PROP_LOW_PERCENT:
       dlbuf->low_percent = g_value_get_int (value);
@@ -1853,6 +1902,10 @@ gst_download_buffer_set_property (GObject * object,
   }
 
   GST_DOWNLOAD_BUFFER_MUTEX_UNLOCK (dlbuf);
+
+  if (msg != NULL)
+    gst_element_post_message (GST_ELEMENT_CAST (dlbuf), msg);
+
 }
 
 static void