dashdemux: synchronize with the download loop thread to signal it to continue

If EOS or ERROR happens before the download loop thread has reached its
g_cond_wait() call, then the g_cond_signal doesn't have any effect and
the download loop thread stucks later.

https://bugzilla.gnome.org/show_bug.cgi?id=735663
This commit is contained in:
George Kiagiadakis 2014-08-29 12:38:12 +02:00 committed by Thiago Santos
parent 01ccac24fa
commit 55032ae5fe
2 changed files with 88 additions and 37 deletions

View File

@ -220,7 +220,7 @@ static GstFlowReturn gst_dash_demux_stream_get_next_fragment (GstDashDemuxStream
static gboolean gst_dash_demux_advance_period (GstDashDemux * demux); static gboolean gst_dash_demux_advance_period (GstDashDemux * demux);
static void gst_dash_demux_download_wait (GstDashDemuxStream * stream, static void gst_dash_demux_download_wait (GstDashDemuxStream * stream,
GstClockTime time_diff); GstClockTime time_diff);
static void gst_dash_demux_stream_download_uri (GstDashDemux * demux, static GstFlowReturn gst_dash_demux_stream_download_uri (GstDashDemux * demux,
GstDashDemuxStream * stream, const gchar * uri, gint64 start, gint64 end); GstDashDemuxStream * stream, const gchar * uri, gint64 start, gint64 end);
static void gst_dash_demux_expose_streams (GstDashDemux * demux); static void gst_dash_demux_expose_streams (GstDashDemux * demux);
@ -236,8 +236,7 @@ static GstPad *gst_dash_demux_create_pad (GstDashDemux * demux,
#define gst_dash_demux_parent_class parent_class #define gst_dash_demux_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstDashDemux, gst_dash_demux, GST_TYPE_BIN, G_DEFINE_TYPE_WITH_CODE (GstDashDemux, gst_dash_demux, GST_TYPE_BIN,
GST_DEBUG_CATEGORY_INIT (gst_dash_demux_debug, "dashdemux", 0, GST_DEBUG_CATEGORY_INIT (gst_dash_demux_debug, "dashdemux", 0,
"dashdemux element"); "dashdemux element"););
);
static void static void
gst_dash_demux_dispose (GObject * obj) gst_dash_demux_dispose (GObject * obj)
@ -428,8 +427,13 @@ gst_dash_demux_handle_message (GstBin * bin, GstMessage * msg)
err->domain, err->code, err->message, debug); err->domain, err->code, err->message, debug);
/* error, but ask to retry */ /* error, but ask to retry */
g_mutex_lock (&stream->fragment_download_lock);
if (!stream->download_finished) {
stream->last_ret = GST_FLOW_CUSTOM_ERROR; stream->last_ret = GST_FLOW_CUSTOM_ERROR;
stream->download_finished = TRUE;
}
g_cond_signal (&stream->fragment_download_cond); g_cond_signal (&stream->fragment_download_cond);
g_mutex_unlock (&stream->fragment_download_lock);
g_error_free (err); g_error_free (err);
g_free (debug); g_free (debug);
@ -601,12 +605,16 @@ gst_dash_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
GstDashDemuxStream *stream = iter->data; GstDashDemuxStream *stream = iter->data;
if (stream->pad == pad) { if (stream->pad == pad) {
g_mutex_lock (&stream->fragment_download_lock);
if (stream->last_ret == GST_FLOW_NOT_LINKED) { if (stream->last_ret == GST_FLOW_NOT_LINKED) {
stream->last_ret = GST_FLOW_OK; stream->last_ret = GST_FLOW_OK;
stream->restart_download = TRUE; stream->restart_download = TRUE;
stream->need_header = TRUE; stream->need_header = TRUE;
g_mutex_unlock (&stream->fragment_download_lock);
GST_DEBUG_OBJECT (stream->pad, "Restarting download loop"); GST_DEBUG_OBJECT (stream->pad, "Restarting download loop");
gst_task_start (stream->download_task); gst_task_start (stream->download_task);
} else {
g_mutex_unlock (&stream->fragment_download_lock);
} }
GST_DASH_DEMUX_CLIENT_UNLOCK (demux); GST_DASH_DEMUX_CLIENT_UNLOCK (demux);
gst_event_unref (event); gst_event_unref (event);
@ -662,12 +670,16 @@ gst_dash_demux_all_streams_eop (GstDashDemux * demux)
for (iter = demux->streams; iter; iter = g_slist_next (iter)) { for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data; GstDashDemuxStream *stream = iter->data;
g_mutex_lock (&stream->fragment_download_lock);
GST_LOG_OBJECT (stream->pad, "EOP: %d (linked: %d)", stream->stream_eos, GST_LOG_OBJECT (stream->pad, "EOP: %d (linked: %d)", stream->stream_eos,
stream->last_ret != GST_FLOW_NOT_LINKED); stream->last_ret != GST_FLOW_NOT_LINKED);
if (!stream->stream_eos && stream->last_ret != GST_FLOW_NOT_LINKED) if (!stream->stream_eos && stream->last_ret != GST_FLOW_NOT_LINKED) {
g_mutex_unlock (&stream->fragment_download_lock);
return FALSE; return FALSE;
} }
g_mutex_unlock (&stream->fragment_download_lock);
}
return TRUE; return TRUE;
} }
@ -724,6 +736,8 @@ gst_dash_demux_setup_all_streams (GstDashDemux * demux)
g_cond_init (&stream->fragment_download_cond); g_cond_init (&stream->fragment_download_cond);
g_mutex_init (&stream->fragment_download_lock); g_mutex_init (&stream->fragment_download_lock);
stream->download_finished = FALSE;
GST_LOG_OBJECT (demux, "Creating stream %d %" GST_PTR_FORMAT, i, caps); GST_LOG_OBJECT (demux, "Creating stream %d %" GST_PTR_FORMAT, i, caps);
streams = g_slist_prepend (streams, stream); streams = g_slist_prepend (streams, stream);
stream->pad = gst_dash_demux_create_pad (demux, stream); stream->pad = gst_dash_demux_create_pad (demux, stream);
@ -1076,12 +1090,15 @@ gst_dash_demux_stop (GstDashDemux * demux)
for (iter = demux->streams; iter; iter = g_slist_next (iter)) { for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data; GstDashDemuxStream *stream = iter->data;
stream->last_ret = GST_FLOW_FLUSHING;
stream->need_header = TRUE; stream->need_header = TRUE;
gst_task_stop (stream->download_task); gst_task_stop (stream->download_task);
GST_TASK_SIGNAL (stream->download_task); GST_TASK_SIGNAL (stream->download_task);
gst_element_set_state (stream->src, GST_STATE_READY); gst_element_set_state (stream->src, GST_STATE_READY);
g_mutex_lock (&stream->fragment_download_lock);
stream->download_finished = TRUE;
stream->last_ret = GST_FLOW_FLUSHING;
g_cond_signal (&stream->fragment_download_cond); g_cond_signal (&stream->fragment_download_cond);
g_mutex_unlock (&stream->fragment_download_lock);
} }
} }
@ -1131,7 +1148,7 @@ gst_dash_demux_remove_streams (GstDashDemux * demux, GSList * streams)
GstEvent *eos = gst_event_new_eos (); GstEvent *eos = gst_event_new_eos ();
for (iter = streams; iter; iter = g_slist_next (iter)) { for (iter = streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data;; GstDashDemuxStream *stream = iter->data;
GST_LOG_OBJECT (stream->pad, "Removing stream %d %" GST_PTR_FORMAT, GST_LOG_OBJECT (stream->pad, "Removing stream %d %" GST_PTR_FORMAT,
stream->index, stream->input_caps); stream->index, stream->input_caps);
@ -1450,13 +1467,17 @@ gst_dash_demux_combine_flows (GstDashDemux * demux)
for (iter = demux->streams; iter; iter = g_slist_next (iter)) { for (iter = demux->streams; iter; iter = g_slist_next (iter)) {
GstDashDemuxStream *stream = iter->data; GstDashDemuxStream *stream = iter->data;
g_mutex_lock (&stream->fragment_download_lock);
if (stream->last_ret != GST_FLOW_NOT_LINKED) if (stream->last_ret != GST_FLOW_NOT_LINKED)
all_notlinked = FALSE; all_notlinked = FALSE;
if (stream->last_ret <= GST_FLOW_NOT_NEGOTIATED if (stream->last_ret <= GST_FLOW_NOT_NEGOTIATED
|| stream->last_ret == GST_FLOW_FLUSHING) || stream->last_ret == GST_FLOW_FLUSHING) {
g_mutex_unlock (&stream->fragment_download_lock);
return stream->last_ret; return stream->last_ret;
} }
g_mutex_unlock (&stream->fragment_download_lock);
}
if (all_notlinked) if (all_notlinked)
return GST_FLOW_NOT_LINKED; return GST_FLOW_NOT_LINKED;
return GST_FLOW_OK; return GST_FLOW_OK;
@ -1524,7 +1545,6 @@ gst_dash_demux_stream_download_loop (GstDashDemuxStream * stream)
goto cancelled; goto cancelled;
} }
stream->last_ret = flow_ret;
switch (flow_ret) { switch (flow_ret) {
case GST_FLOW_OK: case GST_FLOW_OK:
break; break;
@ -1627,13 +1647,13 @@ gst_dash_demux_stream_download_loop (GstDashDemuxStream * stream)
break; break;
} }
if (G_LIKELY (stream->last_ret != GST_FLOW_ERROR)) if (G_LIKELY (flow_ret != GST_FLOW_ERROR))
demux->client->update_failed_count = 0; demux->client->update_failed_count = 0;
quit: quit:
GST_DASH_DEMUX_CLIENT_UNLOCK (demux); GST_DASH_DEMUX_CLIENT_UNLOCK (demux);
if (G_UNLIKELY (stream->last_ret == GST_FLOW_EOS)) { if (G_UNLIKELY (flow_ret == GST_FLOW_EOS)) {
gst_pad_push_event (stream->pad, gst_event_new_eos ()); gst_pad_push_event (stream->pad, gst_event_new_eos ());
} }
@ -1753,12 +1773,13 @@ gst_dash_demux_stream_select_representation_unlocked (GstDashDemuxStream *
return NULL; return NULL;
} }
static void static GstFlowReturn
gst_dash_demux_download_header_fragment (GstDashDemux * demux, gst_dash_demux_download_header_fragment (GstDashDemux * demux,
GstDashDemuxStream * stream, gchar * path, gint64 range_start, GstDashDemuxStream * stream, gchar * path, gint64 range_start,
gint64 range_end) gint64 range_end)
{ {
gchar *next_header_uri; gchar *next_header_uri;
GstFlowReturn ret;
if (strncmp (path, "http://", 7) != 0) { if (strncmp (path, "http://", 7) != 0) {
next_header_uri = next_header_uri =
@ -1769,37 +1790,40 @@ gst_dash_demux_download_header_fragment (GstDashDemux * demux,
next_header_uri = path; next_header_uri = path;
} }
gst_dash_demux_stream_download_uri (demux, stream, next_header_uri, ret = gst_dash_demux_stream_download_uri (demux, stream, next_header_uri,
range_start, range_end); range_start, range_end);
g_free (next_header_uri); g_free (next_header_uri);
return ret;
} }
static void static GstFlowReturn
gst_dash_demux_get_next_header (GstDashDemux * demux, gst_dash_demux_get_next_header (GstDashDemux * demux,
GstDashDemuxStream * stream) GstDashDemuxStream * stream)
{ {
gchar *initializationURL; gchar *initializationURL;
gint64 range_start, range_end; gint64 range_start, range_end;
GstFlowReturn ret;
if (!gst_mpd_client_get_next_header (demux->client, &initializationURL, if (!gst_mpd_client_get_next_header (demux->client, &initializationURL,
stream->index, &range_start, &range_end)) stream->index, &range_start, &range_end))
return; return GST_FLOW_OK;
GST_INFO_OBJECT (demux, "Fetching header %s %" G_GINT64_FORMAT "-%" GST_INFO_OBJECT (demux, "Fetching header %s %" G_GINT64_FORMAT "-%"
G_GINT64_FORMAT, initializationURL, range_start, range_end); G_GINT64_FORMAT, initializationURL, range_start, range_end);
gst_dash_demux_download_header_fragment (demux, stream, ret = gst_dash_demux_download_header_fragment (demux, stream,
initializationURL, range_start, range_end); initializationURL, range_start, range_end);
/* check if we have an index */ /* check if we have an index */
if (!demux->cancelled && stream->last_ret == GST_FLOW_OK /* TODO check for other valid types */ if (!demux->cancelled && ret == GST_FLOW_OK /* TODO check for other valid types */
&& gst_mpd_client_get_next_header_index (demux->client, && gst_mpd_client_get_next_header_index (demux->client,
&initializationURL, stream->index, &range_start, &range_end)) { &initializationURL, stream->index, &range_start, &range_end)) {
GST_INFO_OBJECT (demux, GST_INFO_OBJECT (demux,
"Fetching index %s %" G_GINT64_FORMAT "-%" G_GINT64_FORMAT, "Fetching index %s %" G_GINT64_FORMAT "-%" G_GINT64_FORMAT,
initializationURL, range_start, range_end); initializationURL, range_start, range_end);
gst_dash_demux_download_header_fragment (demux, stream, ret = gst_dash_demux_download_header_fragment (demux, stream,
initializationURL, range_start, range_end); initializationURL, range_start, range_end);
} }
return ret;
} }
static GstCaps * static GstCaps *
@ -2001,10 +2025,12 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
/* TODO properly stop tasks */ /* TODO properly stop tasks */
/* gst_hls_demux_pause_tasks (demux); */ /* gst_hls_demux_pause_tasks (demux); */
g_cond_signal (&stream->fragment_download_cond); g_mutex_lock (&stream->fragment_download_lock);
}
stream->last_ret = ret; stream->last_ret = ret;
stream->download_finished = TRUE;
g_cond_signal (&stream->fragment_download_cond);
g_mutex_unlock (&stream->fragment_download_lock);
}
return ret; return ret;
} }
@ -2017,7 +2043,10 @@ _src_event (GstPad * pad, GstObject * parent, GstEvent * event)
switch (GST_EVENT_TYPE (event)) { switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS: case GST_EVENT_EOS:
g_mutex_lock (&stream->fragment_download_lock);
stream->download_finished = TRUE;
g_cond_signal (&stream->fragment_download_cond); g_cond_signal (&stream->fragment_download_cond);
g_mutex_unlock (&stream->fragment_download_lock);
break; break;
default: default:
break; break;
@ -2076,7 +2105,6 @@ gst_dash_demux_stream_update_source (GstDashDemuxStream * stream,
if (!gst_uri_is_valid (uri)) { if (!gst_uri_is_valid (uri)) {
GST_WARNING_OBJECT (stream->pad, "Invalid URI: %s", uri); GST_WARNING_OBJECT (stream->pad, "Invalid URI: %s", uri);
stream->last_ret = GST_FLOW_ERROR;
return FALSE; return FALSE;
} }
@ -2177,16 +2205,21 @@ gst_dash_demux_stream_update_source (GstDashDemuxStream * stream,
return TRUE; return TRUE;
} }
/* must be called with the stream's fragment_download_lock */ static GstFlowReturn
static void
gst_dash_demux_stream_download_uri (GstDashDemux * demux, gst_dash_demux_stream_download_uri (GstDashDemux * demux,
GstDashDemuxStream * stream, const gchar * uri, gint64 start, gint64 end) GstDashDemuxStream * stream, const gchar * uri, gint64 start, gint64 end)
{ {
GstFlowReturn ret = GST_FLOW_OK;
GST_DEBUG_OBJECT (stream->pad, "Downloading uri: %s, range:%" G_GINT64_FORMAT GST_DEBUG_OBJECT (stream->pad, "Downloading uri: %s, range:%" G_GINT64_FORMAT
" - %" G_GINT64_FORMAT, uri, start, end); " - %" G_GINT64_FORMAT, uri, start, end);
stream->download_finished = FALSE;
if (!gst_dash_demux_stream_update_source (stream, uri, NULL, FALSE, TRUE)) { if (!gst_dash_demux_stream_update_source (stream, uri, NULL, FALSE, TRUE)) {
return; g_mutex_lock (&stream->fragment_download_lock);
ret = stream->last_ret = GST_FLOW_ERROR;
g_mutex_unlock (&stream->fragment_download_lock);
return ret;
} }
if (gst_element_set_state (stream->src, if (gst_element_set_state (stream->src,
@ -2205,18 +2238,30 @@ gst_dash_demux_stream_download_uri (GstDashDemux * demux,
} }
} }
g_mutex_lock (&stream->fragment_download_lock);
if (G_LIKELY (stream->last_ret == GST_FLOW_OK)) { if (G_LIKELY (stream->last_ret == GST_FLOW_OK)) {
g_mutex_unlock (&stream->fragment_download_lock);
stream->download_start_time = g_get_monotonic_time (); stream->download_start_time = g_get_monotonic_time ();
gst_element_sync_state_with_parent (stream->src); gst_element_sync_state_with_parent (stream->src);
g_mutex_lock (&stream->fragment_download_lock);
/* wait for the fragment to be completely downloaded */ /* wait for the fragment to be completely downloaded */
GST_DEBUG_OBJECT (stream->pad, GST_DEBUG_OBJECT (stream->pad,
"Waiting for fragment download to finish: %s", uri); "Waiting for fragment download to finish: %s", uri);
while (!demux->cancelled && !stream->download_finished) {
g_cond_wait (&stream->fragment_download_cond, g_cond_wait (&stream->fragment_download_cond,
&stream->fragment_download_lock); &stream->fragment_download_lock);
} }
ret = stream->last_ret;
GST_DEBUG_OBJECT (stream->pad, "Fragment download finished: %s", uri);
}
g_mutex_unlock (&stream->fragment_download_lock);
} else { } else {
stream->last_ret = GST_FLOW_CUSTOM_ERROR; g_mutex_lock (&stream->fragment_download_lock);
ret = stream->last_ret = GST_FLOW_CUSTOM_ERROR;
g_mutex_unlock (&stream->fragment_download_lock);
} }
/* flush the proxypads so that the EOS state is reset */ /* flush the proxypads so that the EOS state is reset */
@ -2224,6 +2269,8 @@ gst_dash_demux_stream_download_uri (GstDashDemux * demux,
gst_pad_push_event (stream->src_srcpad, gst_event_new_flush_stop (TRUE)); gst_pad_push_event (stream->src_srcpad, gst_event_new_flush_stop (TRUE));
gst_element_set_state (stream->src, GST_STATE_READY); gst_element_set_state (stream->src, GST_STATE_READY);
gst_dash_demux_stream_clear_eos_state (stream); gst_dash_demux_stream_clear_eos_state (stream);
return ret;
} }
static void static void
@ -2233,6 +2280,7 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
GstActiveStream *active_stream; GstActiveStream *active_stream;
guint stream_idx = stream->index; guint stream_idx = stream->index;
GstMediaFragmentInfo *fragment = &stream->current_fragment; GstMediaFragmentInfo *fragment = &stream->current_fragment;
GstFlowReturn ret = GST_FLOW_OK;
if (G_UNLIKELY (stream->restart_download)) { if (G_UNLIKELY (stream->restart_download)) {
GstClockTime cur, ts; GstClockTime cur, ts;
@ -2294,7 +2342,6 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
stream->restart_download = FALSE; stream->restart_download = FALSE;
} }
g_mutex_lock (&stream->fragment_download_lock);
if (gst_mpd_client_get_next_fragment (demux->client, stream_idx, fragment, if (gst_mpd_client_get_next_fragment (demux->client, stream_idx, fragment,
stream->demux->segment.rate > 0.0)) { stream->demux->segment.rate > 0.0)) {
GST_INFO_OBJECT (stream->pad, GST_INFO_OBJECT (stream->pad,
@ -2305,16 +2352,18 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
fragment->range_start, fragment->range_end); fragment->range_start, fragment->range_end);
/* Reset last flow return */ /* Reset last flow return */
g_mutex_lock (&stream->fragment_download_lock);
stream->last_ret = GST_FLOW_OK; stream->last_ret = GST_FLOW_OK;
stream->starting_fragment = TRUE; stream->starting_fragment = TRUE;
g_mutex_unlock (&stream->fragment_download_lock);
if (stream->need_header) { if (stream->need_header) {
/* We need to fetch a new header */ /* We need to fetch a new header */
gst_dash_demux_get_next_header (demux, stream); ret = gst_dash_demux_get_next_header (demux, stream);
stream->need_header = FALSE; stream->need_header = FALSE;
} }
if (stream->last_ret != GST_FLOW_OK) { if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (stream->pad, "Failed to download headers"); GST_WARNING_OBJECT (stream->pad, "Failed to download headers");
goto exit; goto exit;
} }
@ -2334,11 +2383,11 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
"Fragment index download: %s %" G_GINT64_FORMAT "-%" "Fragment index download: %s %" G_GINT64_FORMAT "-%"
G_GINT64_FORMAT, uri, fragment->index_range_start, G_GINT64_FORMAT, uri, fragment->index_range_start,
fragment->index_range_end); fragment->index_range_end);
gst_dash_demux_stream_download_uri (demux, stream, uri, ret = gst_dash_demux_stream_download_uri (demux, stream, uri,
fragment->index_range_start, fragment->index_range_end); fragment->index_range_start, fragment->index_range_end);
} }
if (stream->last_ret != GST_FLOW_OK) { if (ret != GST_FLOW_OK) {
GST_WARNING_OBJECT (stream->pad, "Failed to download fragment headers"); GST_WARNING_OBJECT (stream->pad, "Failed to download fragment headers");
goto exit; goto exit;
} }
@ -2347,10 +2396,10 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
goto exit; goto exit;
/* now get the real fragment */ /* now get the real fragment */
gst_dash_demux_stream_download_uri (demux, stream, fragment->uri, ret = gst_dash_demux_stream_download_uri (demux, stream, fragment->uri,
fragment->range_start, fragment->range_end); fragment->range_start, fragment->range_end);
if (stream->last_ret < GST_FLOW_EOS) { if (ret < GST_FLOW_EOS) {
gst_media_fragment_info_clear (fragment); gst_media_fragment_info_clear (fragment);
goto exit; goto exit;
} }
@ -2358,11 +2407,14 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
active_stream = stream->active_stream; active_stream = stream->active_stream;
if (active_stream == NULL) { if (active_stream == NULL) {
gst_media_fragment_info_clear (fragment); gst_media_fragment_info_clear (fragment);
g_mutex_lock (&stream->fragment_download_lock);
ret = GST_FLOW_ERROR;
stream->last_ret = GST_FLOW_ERROR; stream->last_ret = GST_FLOW_ERROR;
g_mutex_unlock (&stream->fragment_download_lock);
goto exit; goto exit;
} }
if (stream->last_ret == GST_FLOW_OK) { if (ret == GST_FLOW_OK) {
stream->position += fragment->duration; stream->position += fragment->duration;
} }
@ -2374,8 +2426,6 @@ gst_dash_demux_stream_download_fragment (GstDashDemux * demux,
exit: exit:
g_mutex_unlock (&stream->fragment_download_lock);
if (stream->last_ret == GST_FLOW_OK) if (stream->last_ret == GST_FLOW_OK)
gst_element_set_state (stream->src, GST_STATE_READY); gst_element_set_state (stream->src, GST_STATE_READY);
else else

View File

@ -86,6 +86,7 @@ struct _GstDashDemuxStream
GstPad *src_srcpad; GstPad *src_srcpad;
GMutex fragment_download_lock; GMutex fragment_download_lock;
GCond fragment_download_cond; GCond fragment_download_cond;
gboolean download_finished;
GstMediaFragmentInfo current_fragment; GstMediaFragmentInfo current_fragment;
gboolean starting_fragment; gboolean starting_fragment;
gint64 download_start_time; gint64 download_start_time;