diff options
author | Edward Hervey <bilboed@bilboed.com> | 2014-07-23 09:29:27 +0200 |
---|---|---|
committer | Edward Hervey <bilboed@bilboed.com> | 2014-07-23 09:29:27 +0200 |
commit | 8e4a97356217ee46290d3af69a507c7ccdc3dbc7 (patch) | |
tree | 9ec513299057459a8cb739b507a0b9744830fb0e | |
parent | 0e78c42ebe66e61cd72c90a1d4e92cc78e704f0b (diff) |
WIP: dlnasrc: Refactor/simplify seek handlingrefactoring
Key changes:
* decision is made as early as possible whether a certain seek event
can be handled, based on server capabilities
* first seek event will *always* be a TIME-based seek
* decision is made as early as possible regarding what server technique
will be used (Range, TimeSeekRange, PlaySpeed)
* Create adequate BYTE-only seek event for souphttpsrc (which can only
handle BYTE seeks)
* Intercept outgoing SEGMENT event and modify it based on initial
requested seek
* Intercept outgoing Request/Response HTTP headers from souphttpsrc, which
will be used to properly identify what the server did, and should be
used to properly fill in outgoing SEGMENT event
TODO:
* Use actual server response (HTTP Response headers from souphttpsrc) to
fill in the outgoing SEGMENT event.
** PlaySpeed => set rate:1.0, applied_rate:<actual rate the server specifies>
** Both => set start/stop/time to returned npt values
* Handle time-based scan mode. Essentially the client needs to continuously
fetch "ranges" of data from the server. The size of that "range" should be
configurable.
* Handle TIME<=>BYTES conversion if the server does not support TimeSeekRange
nor PlaySpeed.
* Handle byte-based scan mode. Same as for time-based ... except you will need
to figure out the TIME=>BYTE conversion.
-rw-r--r-- | src/gstdlnasrc.c | 522 | ||||
-rw-r--r-- | src/gstdlnasrc.h | 27 |
2 files changed, 307 insertions, 242 deletions
diff --git a/src/gstdlnasrc.c b/src/gstdlnasrc.c index 359b28a..9e95017 100644 --- a/src/gstdlnasrc.c +++ b/src/gstdlnasrc.c @@ -307,7 +307,7 @@ static void dlna_src_struct_append_header_value_str_guint64 (GString * struct_str, gchar * title, gchar * value_str, guint64 value); static gboolean dlna_src_handle_event_seek (GstDlnaSrc * dlna_src, - GstPad * pad, GstEvent * event); + GstPad * pad, GstEvent * event, GstEvent **new_event); static gboolean dlna_src_handle_query_duration (GstDlnaSrc * dlna_src, GstQuery * query); @@ -315,9 +315,6 @@ static gboolean dlna_src_handle_query_duration (GstDlnaSrc * dlna_src, static gboolean dlna_src_handle_query_seeking (GstDlnaSrc * dlna_src, GstQuery * query); -static gboolean dlna_src_handle_query_segment (GstDlnaSrc * dlna_src, - GstQuery * query); - static gboolean dlna_src_handle_query_convert (GstDlnaSrc * dlna_src, GstQuery * query); @@ -330,15 +327,15 @@ dlna_src_parse_npt_range (GstDlnaSrc * dlna_src, const gchar * npt_str, gchar ** start_str, gchar ** stop_str, gchar ** total_str, guint64 * start, guint64 * stop, guint64 * total); +#if 0 static gboolean dlna_src_is_change_valid (GstDlnaSrc * dlna_src, gfloat rate, GstFormat format, guint64 start, GstSeekType start_type, guint64 stop, GstSeekType stop_type); +#endif static gboolean dlna_src_is_rate_supported (GstDlnaSrc * dlna_src, gfloat rate); -static gboolean dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src, - gfloat rate, GstFormat format, guint64 start, guint64 stop, - guint32 new_seqnum); +static gboolean dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src); static gboolean dlna_src_npt_to_nanos (GstDlnaSrc * dlna_src, gchar * string, guint64 * media_time_nanos); @@ -437,15 +434,10 @@ gst_dlna_src_init (GstDlnaSrc * dlna_src) dlna_src->server_info = NULL; - dlna_src->rate = 1.0; - dlna_src->requested_rate = 1.0; - dlna_src->requested_format = GST_FORMAT_BYTES; - dlna_src->requested_start = 0; - dlna_src->requested_stop = -1; + gst_segment_init (&dlna_src->requested_segment, GST_FORMAT_UNDEFINED); + dlna_src->seek_seqnum = 0; - dlna_src->time_seek_seqnum = 0; dlna_src->time_seek_event_start = 0; - dlna_src->handled_time_seek_seqnum = FALSE; dlna_src->is_uri_initialized = FALSE; dlna_src->is_live = FALSE; @@ -638,13 +630,19 @@ static gboolean gst_dlna_src_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean ret = FALSE; + GstEvent *new_event = NULL; GstDlnaSrc *dlna_src = GST_DLNA_SRC (gst_pad_get_parent (pad)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: - GST_INFO_OBJECT (dlna_src, "Got src event: %s", - GST_EVENT_TYPE_NAME (event)); - ret = dlna_src_handle_event_seek (dlna_src, pad, event); + { + GST_INFO_OBJECT (dlna_src, "Got src event: %s", + GST_EVENT_TYPE_NAME (event)); + ret = dlna_src_handle_event_seek (dlna_src, pad, event, &new_event); + /* If we can handle it, substitute (potentially) modified seek event */ + if (ret) + event = new_event; + } break; case GST_EVENT_FLUSH_START: @@ -670,9 +668,12 @@ gst_dlna_src_event (GstPad * pad, GstObject * parent, GstEvent * event) break; } - /* If not handled, pass on to default pad handler */ - if (!ret) { + /* If wasn't refused locally, pass on to default pad handler */ + if (ret) { ret = gst_pad_event_default (pad, parent, event); + /* If we created a new event, unref it */ + if (new_event) + gst_event_unref (new_event); } return ret; @@ -704,10 +705,6 @@ gst_dlna_src_query (GstPad * pad, GstObject * parent, GstQuery * query) ret = dlna_src_handle_query_seeking (dlna_src, query); break; - case GST_QUERY_SEGMENT: - ret = dlna_src_handle_query_segment (dlna_src, query); - break; - case GST_QUERY_CONVERT: ret = dlna_src_handle_query_convert (dlna_src, query); break; @@ -724,9 +721,12 @@ gst_dlna_src_query (GstPad * pad, GstObject * parent, GstQuery * query) ret = TRUE; break; +#if 0 + /* REVIEW: dlnasrc is not a live element, you do not need to handle it */ case GST_QUERY_LATENCY: /* Don't know latency, let some other element handle this */ break; +#endif case GST_QUERY_POSITION: /* Don't know current position in stream, let some other element handle this */ @@ -867,70 +867,6 @@ dlna_src_handle_query_seeking (GstDlnaSrc * dlna_src, GstQuery * query) return ret; } -/** - * Responds to a segment query by returning rate along with start and stop - * - * @param dlna_src this element - * @param query received segment query to respond to - * - * @return true if responded to query, false otherwise - */ -static gboolean -dlna_src_handle_query_segment (GstDlnaSrc * dlna_src, GstQuery * query) -{ - gboolean ret = FALSE; - GstFormat format; - gdouble rate = 1.0; - gint64 start = 0; - gint64 end = 0; - - GST_LOG_OBJECT (dlna_src, "Called"); - - if ((dlna_src->uri == NULL) || (dlna_src->server_info == NULL)) { - GST_INFO_OBJECT (dlna_src, - "No URI and/or HEAD response info, unable to handle query"); - return FALSE; - } - gst_query_parse_segment (query, &rate, &format, &start, &end); - - if (format == GST_FORMAT_BYTES) { - if (dlna_src->byte_seek_supported) { - - gst_query_set_segment (query, dlna_src->rate, GST_FORMAT_BYTES, - dlna_src->byte_start, dlna_src->byte_end); - ret = TRUE; - - GST_DEBUG_OBJECT (dlna_src, - "Segment info in bytes for this content, rate %f, start %" - G_GUINT64_FORMAT ", end %" G_GUINT64_FORMAT, - dlna_src->rate, dlna_src->byte_start, dlna_src->byte_end); - } else - GST_DEBUG_OBJECT (dlna_src, - "Segment info in bytes not available for content item"); - } else if (format == GST_FORMAT_TIME) { - - if (dlna_src->time_seek_supported) { - gst_query_set_segment (query, dlna_src->rate, GST_FORMAT_TIME, - dlna_src->npt_start_nanos, dlna_src->npt_end_nanos); - ret = TRUE; - - GST_DEBUG_OBJECT (dlna_src, - "Time based segment info for this content by the server, rate %f, start %" - GST_TIME_FORMAT ", end %" GST_TIME_FORMAT, - dlna_src->rate, - GST_TIME_ARGS (dlna_src->npt_start_nanos), - GST_TIME_ARGS (dlna_src->npt_end_nanos)); - } else - GST_DEBUG_OBJECT (dlna_src, - "Segment info in media time not available for content item"); - } else { - GST_DEBUG_OBJECT (dlna_src, - "Got segment query with non-supported format type: %s, passing to default handler", - GST_QUERY_TYPE_NAME (query)); - } - - return ret; -} /** * Responds to a convert query by issuing head and returning conversion value @@ -952,7 +888,7 @@ dlna_src_handle_query_convert (GstDlnaSrc * dlna_src, GstQuery * query) GST_LOG_OBJECT (dlna_src, "Called"); - if (dlna_src->uri == NULL || !dlna_src->time_seek_supported) { + if (dlna_src->uri == NULL) { GST_INFO_OBJECT (dlna_src, "Not enough info to handle conversion query"); return FALSE; } @@ -1004,70 +940,150 @@ dlna_src_handle_query_convert (GstDlnaSrc * dlna_src, GstQuery * query) * Perform action necessary when seek event is received * * @param dlna_src this element - * @param seek_event seek event which has been received + * @param event seek event which has been received + * @param return_event (updated) seek_event to send to souphttpsrc * - * @return true if this event has been handled, false otherwise + * @return true if the seek can be handled, else false */ static gboolean dlna_src_handle_event_seek (GstDlnaSrc * dlna_src, GstPad * pad, - GstEvent * event) + GstEvent *event, GstEvent **return_event) { GST_LOG_OBJECT (dlna_src, "Handle seek event"); gdouble rate; GstFormat format; GstSeekFlags flags; - GstSeekType start_type; - gint64 start; - GstSeekType stop_type; - gint64 stop; - guint32 new_seqnum; + GstSeekType start_type, stop_type; + gint64 start, stop; +#if 0 gboolean convert_start = FALSE; +#endif + GstDlnaTrickMode trick_mode = GST_DLNA_MODE_NONE; + + /* WIP: Improve seek handling + * souphttpsrc can only handle BYTE seeks, but we'll be sending a bogus BYTE seek + * event to it, unless we have working in GST_DLNA_MODE_RANGE. + * + * 1) Parse the seek values + * 2) If the server can handle the requested format (TIME or BYTES), use that + * 3) If the server cannot handle the requested format, attempt to convert it + * + * Note: If a BYTE seek event is seen before a TIME seek event, this is a sign + * of a non-standard GStreamer pipeline usage. + * + * If a seek of a given seqnum was previously handled successfully, following + * seek events with the same seqnum shall be refused, regardless of format. + */ + /* FIXME/Unclear + * * In case of "live" elements (which increasing s0/sN), we need to get the + * updated range before confirming whether the seek can be handled or not + */ + + gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); + + GST_INFO_OBJECT (dlna_src, + "Got Seek event %d: rate: %3.1f, format: %s, flags: %d, start type: %d, start: %" + G_GUINT64_FORMAT ", stop type: %d, stop: %" + G_GUINT64_FORMAT, gst_event_get_seqnum (event), rate, + gst_format_get_name (format), flags, start_type, start, stop_type, stop); if ((dlna_src->uri == NULL) || (dlna_src->server_info == NULL)) { GST_INFO_OBJECT (dlna_src, "No URI and/or HEAD response info, event handled"); - return TRUE; + return FALSE; } - new_seqnum = gst_event_get_seqnum (event); - if (new_seqnum == dlna_src->time_seek_seqnum) { - if (dlna_src->handled_time_seek_seqnum) { - GST_INFO_OBJECT (dlna_src, "Already processed seek event %d", new_seqnum); - return FALSE; - } else - /* *TODO* - see dlnasrc issue #63 - Got same event now byte based since some element converted it to bytes - Convert here if time seek is supported since it is more accurate */ + /* If we already handled that seek, return straight away succesfully */ + if (gst_event_get_seqnum(event) == dlna_src->seek_seqnum) { + GST_DEBUG_OBJECT (dlna_src, "Already handled seek with the same seqnum"); + return FALSE; + } + + /* Can we handle the seek event directly ? */ + if (format == GST_FORMAT_BYTES) { + /* If the server can handle Range queries and the requested positions are within + * the server range, use it */ + if (dlna_src->byte_seek_supported) { + /* FIXME: support BYTE-based scan-mode ... which is insanely tricky + * It would need to work similarly to the TIME-based scan-mode mentionned + * below... except that we would need some hints as to what offset in bytes + * corresponds to which position in time + */ + if (rate != 1.0) { + GST_WARNING_OBJECT (dlna_src, "FIXME: Implement byte-based scan-mode"); + return FALSE; + } + + /* FIXME, For now we just refuse seeks outside of server byte range, need to + * check DLNA specs to see what the client is meant to do. It might need to clamp + * the requested range */ + if (start < dlna_src->byte_start || start > dlna_src->byte_end) { + GST_WARNING_OBJECT (dlna_src, + "Specified start byte %" G_GUINT64_FORMAT + " is not valid, valid range: %" G_GUINT64_FORMAT + " to %" G_GUINT64_FORMAT, start, + dlna_src->byte_start, dlna_src->byte_end); + return FALSE; + } + + /* Regular BYTE-based playback */ + trick_mode = GST_DLNA_MODE_RANGE; + goto process_seek; + } + } else if (format == GST_FORMAT_TIME) { + /* Server supports playspeed and the requested rate */ + if (rate != 1.0 && dlna_src_is_rate_supported (dlna_src, rate)) { + trick_mode = GST_DLNA_MODE_PLAYSPEED; + goto process_seek; + } + if (dlna_src->time_seek_supported) { - convert_start = TRUE; - GST_INFO_OBJECT (dlna_src, - "Set flag to convert time based seek to byte since server does support time seeks"); - } else - GST_INFO_OBJECT (dlna_src, - "Unable to convert time based seek to byte since server does not support time seeks"); + if (rate != 1.0) { + /* FIXME: in order to support rate != 1.0 when the server doesn't support + * playspeed, we need to requested "chunks" of the stream (using time seek) + * in a loop */ + GST_WARNING_OBJECT (dlna_src, "FIXME: Implement time-based scan mode"); + return FALSE; + } + if (((start == -1) || (start >= dlna_src->npt_start_nanos && start <= dlna_src->npt_end_nanos)) && + ((stop == -1) || (stop >= dlna_src->npt_start_nanos && stop <= dlna_src->npt_end_nanos))) { + trick_mode = GST_DLNA_MODE_TIMERANGE; + goto process_seek; + } + + /* FIXME, For now we just refuse seeks outside of server time range, need to + * check DLNA specs to see what the client is meant to do. It might need to clamp + * the requested range */ + GST_WARNING_OBJECT (dlna_src, "Requested time seek outside of server range"); + return FALSE; + } } else { - dlna_src->handled_time_seek_seqnum = FALSE; - dlna_src->time_seek_seqnum = new_seqnum; - dlna_src->time_seek_event_start = 0; + GST_WARNING_OBJECT (dlna_src, "Seek event in unhandled format"); + return FALSE; } - gst_event_parse_seek (event, &rate, (GstFormat *) & format, - (GstSeekFlags *) & flags, - (GstSeekType *) & start_type, (gint64 *) & start, - (GstSeekType *) & stop_type, (gint64 *) & stop); - - GST_INFO_OBJECT (dlna_src, - "Got Seek event %d: rate: %3.1f, format: %s, flags: %d, start type: %d, start: %" - G_GUINT64_FORMAT ", stop type: %d, stop: %" - G_GUINT64_FORMAT, gst_event_get_seqnum (event), rate, - gst_format_get_name (format), flags, start_type, start, stop_type, stop); + /* FIXME: If we end up here, it means that the server can't handle the + * requested format, we need to convert it + */ + GST_WARNING_OBJECT (dlna_src, "FIXME : Implement BYTE<=>TIME conversion"); + return FALSE; + process_seek: + /* IMPORTANT + * From this point forwards, we *know* the seek will succeed + * IMPORTANT + */ +#if 0 if (convert_start) { GST_INFO_OBJECT (dlna_src, "Supplied start byte %" G_GUINT64_FORMAT " ignored, converting seek event start time %" GST_TIME_FORMAT " to bytes", start, GST_TIME_ARGS (dlna_src->time_seek_event_start)); + /* FIXME: This makes *no* sense whatsoever. + * So the servert doesn't handle TimeSeek ... so you attempt to convert + * a npt position query to bytes by doing a HEAD request on the server ... + * by using "TimeSeekRange.dlna.org: npt=" ... ????? */ if (!dlna_src_convert_npt_nanos_to_bytes (dlna_src, dlna_src->time_seek_event_start, (guint64 *) & start)) { GST_WARNING_OBJECT (dlna_src, "Problems converting to bytes"); @@ -1081,43 +1097,52 @@ dlna_src_handle_event_seek (GstDlnaSrc * dlna_src, GstPad * pad, return TRUE; } - /* *TODO* - is this needed here??? Assign play rate to supplied rate */ - dlna_src->rate = rate; - - dlna_src->requested_rate = rate; - dlna_src->requested_format = format; - dlna_src->requested_start = start; - dlna_src->requested_stop = GST_CLOCK_TIME_NONE; - if (stop > -1) - dlna_src->requested_stop = stop; +#endif + /* Store values in requested segment */ + gst_segment_init (&dlna_src->requested_segment, format); + dlna_src->requested_segment.start = start; + dlna_src->requested_segment.stop = stop; + dlna_src->requested_segment.rate = rate; + dlna_src->seek_seqnum = gst_event_get_seqnum(event); + dlna_src->trick_mode = trick_mode; + +#if 0 + /* FIXME: This sounds weird. Isn't there a reliable mechanism to ask the server + * to send data from the current live position ? + * furthermore ... "< 0" ?? + */ if (dlna_src->is_live && stop < 0) { dlna_src->requested_stop = dlna_src->npt_end_nanos; GST_INFO_OBJECT (dlna_src, "Set requested stop to end of range since content is live: %" G_GUINT64_FORMAT, dlna_src->requested_stop); } +#endif + /* FIXME/WARNING, something doesn't make sense, adjust_http_src_headers() has codepath + * that supposedly work with FORMAT_BYTES... */ /* ignore GST_FORMAT_BYTES from inside GStreamer, don't do http header adjust */ - if ((dlna_src->uri) && (dlna_src->server_info) && - (dlna_src->server_info->content_features != NULL) && + if (dlna_src->server_info->content_features != NULL && (format == GST_FORMAT_TIME)) { - if (!dlna_src_adjust_http_src_headers (dlna_src, dlna_src->requested_rate, - dlna_src->requested_format, dlna_src->requested_start, - dlna_src->requested_stop, new_seqnum)) { + if (!dlna_src_adjust_http_src_headers (dlna_src)) { GST_ERROR_OBJECT (dlna_src, "Problems adjusting soup http src headers"); - /* Returning true to prevent further processing */ - return TRUE; + return FALSE; } - } else + /* Finally create a new bogus BYTE seek event (proper segment will be modified in return) */ + *return_event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, flags, GST_SEEK_TYPE_SET, 0, + GST_SEEK_TYPE_NONE, -1); + } else { GST_INFO_OBJECT (dlna_src, "No header adjustments since server only supports Range requests"); + /* Forward event as-is */ + *return_event = gst_event_ref (event); + } - GST_DEBUG_OBJECT (dlna_src, - "returning false to make sure souphttpsrc gets chance to process"); - return FALSE; + return TRUE; } +#if 0 /** * Determines if the requested rate and/or position change is valid. Seek type is * ignored since the position is always treated as absolute since a position must be @@ -1158,7 +1183,6 @@ dlna_src_is_change_valid (GstDlnaSrc * dlna_src, gfloat rate, if (format == GST_FORMAT_BYTES) { if (dlna_src->byte_seek_supported) { - if (start < dlna_src->byte_start || start > dlna_src->byte_end) { GST_WARNING_OBJECT (dlna_src, "Specified start byte %" G_GUINT64_FORMAT @@ -1182,6 +1206,8 @@ dlna_src_is_change_valid (GstDlnaSrc * dlna_src, gfloat rate, } else if (format == GST_FORMAT_TIME) { if (dlna_src->time_seek_supported) { + /* FIXME : Seeking in ... live ?? + * Shouldn't this be handled in a completely different codepath ? */ if (dlna_src->is_live) { GST_INFO_OBJECT (dlna_src, "Update live content range info"); if (!dlna_src_soup_issue_head (dlna_src, @@ -1234,6 +1260,7 @@ dlna_src_is_change_valid (GstDlnaSrc * dlna_src, gfloat rate, return TRUE; } +#endif /** * Determines if current rate is supported by server based on current @@ -1275,19 +1302,16 @@ dlna_src_is_rate_supported (GstDlnaSrc * dlna_src, gfloat rate) * postion and rate. Instruct souphttpsrc to exclude range header if it conflicts * with any of the extra headers which have been added. * + * The requested_segment and seqnum should be initialized with requested range/rate/.. + * before calling this function + * * @param dlna_src this element - * @param rate requested rate to include in playspeed header - * @param format create either time or byte based seek header - * @param start starting position to include, will be either bytes or time depending on format - * @param stop stopping position to include, will be -1 if unspecified * * @return true if extra headers were successfully created, false otherwise */ static gboolean -dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src, gfloat rate, - GstFormat format, guint64 start, guint64 stop, guint32 new_seqnum) +dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src) { - GValue struct_value = G_VALUE_INIT; GstStructure *extra_headers_struct; gboolean disable_range_header = FALSE; @@ -1311,11 +1335,9 @@ dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src, gfloat rate, gchar range_dtcp_field_value[64] = { 0 }; /* Make sure range header is included by default */ - GValue boolean_value = G_VALUE_INIT; - g_value_init (&boolean_value, G_TYPE_BOOLEAN); - g_value_set_boolean (&boolean_value, FALSE); - g_object_set_property (G_OBJECT (dlna_src->http_src), "exclude-range-header", - &boolean_value); + /* FIXME : Replace all occurences of g_object_set_property() with + * the smaller/saner g_object_set() */ + g_object_set (dlna_src->http_src, "exclude-range-header", FALSE, NULL); /* Create header structure with dlna transfer mode header */ extra_headers_struct = gst_structure_new ("extraHeadersStruct", @@ -1325,19 +1347,20 @@ dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src, gfloat rate, return FALSE; } + /* First try PLAYSPEED */ /* If rate != 1.0, add playspeed header and time seek range header */ - if (rate != 1.0) { + if (dlna_src->trick_mode == GST_DLNA_MODE_PLAYSPEED) { /* Get string representation of rate (use original values to make fractions easy like 1/3) */ for (i = 0; i < dlna_src->server_info->content_features->playspeeds_cnt; i++) { - if (dlna_src->server_info->content_features->playspeeds[i] == rate) { + if (dlna_src->server_info->content_features->playspeeds[i] == dlna_src->requested_segment.rate) { rateStr = dlna_src->server_info->content_features->playspeed_strs[i]; break; } } if (rateStr == NULL) { GST_ERROR_OBJECT (dlna_src, - "Unable to get string representation of supported rate: %lf", rate); + "Unable to get string representation of supported rate: %lf", dlna_src->requested_segment.rate); return FALSE; } g_snprintf (playspeed_field_value, 64, "%s%s", @@ -1351,86 +1374,49 @@ dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src, gfloat rate, } /* Add time seek header for all non 1x rates or for time based seeks */ - if (rate != 1.0 || (format == GST_FORMAT_TIME - && dlna_src->time_seek_supported)) { - if (format != GST_FORMAT_TIME) { - if (!dlna_src_convert_bytes_to_npt_nanos (dlna_src, start, - &start_time_nanos)) { - GST_WARNING_OBJECT (dlna_src, - "Problems converting start %" G_GUINT64_FORMAT " to npt ", start); - return FALSE; - } else - GST_INFO_OBJECT (dlna_src, - "Due to non 1x playspeed, converted start %" G_GUINT64_FORMAT - " bytes into time %" G_GUINT64_FORMAT, start, start_time_nanos); - - if (stop > GST_CLOCK_TIME_NONE) { - if (!dlna_src_convert_bytes_to_npt_nanos (dlna_src, stop, - &stop_time_nanos)) { - GST_WARNING_OBJECT (dlna_src, - "Problems converting stop %" G_GUINT64_FORMAT " to npt ", stop); - return FALSE; - } else - GST_INFO_OBJECT (dlna_src, - "Due to non 1x playspeed, converted stop %" G_GUINT64_FORMAT - " bytes into time %" G_GUINT64_FORMAT, start, start_time_nanos); - } else - GST_INFO_OBJECT (dlna_src, "Stop undefined, no conversion needed"); - } else { - /* Already in time format, no conversion necessary */ - start_time_nanos = start; - stop_time_nanos = stop; - GST_INFO_OBJECT (dlna_src, - "Using start %" G_GUINT64_FORMAT " and stop %" G_GUINT64_FORMAT, - start_time_nanos, stop_time_nanos); - } + if (dlna_src->trick_mode == GST_DLNA_MODE_PLAYSPEED || + dlna_src->trick_mode == GST_DLNA_MODE_TIMERANGE) { + /* Already in time format, no conversion necessary */ + start_time_nanos = dlna_src->requested_segment.start; + stop_time_nanos = dlna_src->requested_segment.stop; + GST_INFO_OBJECT (dlna_src, + "Using start %" G_GUINT64_FORMAT " and stop %" G_GUINT64_FORMAT, + start_time_nanos, stop_time_nanos); /* Convert time from nanos into secs for header value string */ - if (dlna_src->time_seek_supported) { - start_time_secs = start_time_nanos / GST_SECOND; - - if (stop_time_nanos == GST_CLOCK_TIME_NONE) { - GST_INFO_OBJECT (dlna_src, - "Stop time is undefined, just including start time"); - g_snprintf (time_seek_range_field_value, 64, - "%s%" G_GUINT64_FORMAT ".0-", time_seek_range_field_value_prefix, - start_time_secs); - } else { - stop_time_secs = stop_time_nanos / GST_SECOND; - GST_INFO_OBJECT (dlna_src, "Including stop secs: %" G_GUINT64_FORMAT, - stop_time_secs); - g_snprintf (time_seek_range_field_value, 64, - "%s%" G_GUINT64_FORMAT ".0-%" G_GUINT64_FORMAT ".0", - time_seek_range_field_value_prefix, start_time_secs, stop_time_secs); - } - gst_structure_set (extra_headers_struct, time_seek_range_field_name, - G_TYPE_STRING, &time_seek_range_field_value, NULL); + start_time_secs = start_time_nanos / GST_SECOND; + if (stop_time_nanos == GST_CLOCK_TIME_NONE) { GST_INFO_OBJECT (dlna_src, - "Adjust headers by including TimeSeekRange header value: %s", - time_seek_range_field_value); + "Stop time is undefined, just including start time"); + g_snprintf (time_seek_range_field_value, 64, + "%s%" G_GUINT64_FORMAT ".0-", time_seek_range_field_value_prefix, + start_time_secs); + } else { + stop_time_secs = stop_time_nanos / GST_SECOND; + GST_INFO_OBJECT (dlna_src, "Including stop secs: %" G_GUINT64_FORMAT, + stop_time_secs); + g_snprintf (time_seek_range_field_value, 64, + "%s%" G_GUINT64_FORMAT ".0-%" G_GUINT64_FORMAT ".0", + time_seek_range_field_value_prefix, start_time_secs, stop_time_secs); } + gst_structure_set (extra_headers_struct, time_seek_range_field_name, + G_TYPE_STRING, &time_seek_range_field_value, NULL); + + GST_INFO_OBJECT (dlna_src, + "Adjust headers by including TimeSeekRange header value: %s", + time_seek_range_field_value); /* Set flag so range header is not included by souphttpsrc */ disable_range_header = TRUE; - - /* Record seqnum since have handled this event as time based seek */ - dlna_src->handled_time_seek_seqnum = TRUE; } else GST_INFO_OBJECT (dlna_src, "Not adjusting with playspeed or time seek range "); - if (format == GST_FORMAT_TIME && !dlna_src->time_seek_supported) { - GST_INFO_OBJECT (dlna_src, - "Saving start time incase get this event again in bytes in order to do acurate conversion"); - dlna_src->time_seek_event_start = start; - } - /* If dtcp protected content and rate = 1.0, add range.dtcp.com header */ - if (rate == 1.0 && format == GST_FORMAT_BYTES - && dlna_src->is_encrypted && dlna_src->byte_seek_supported) { + if (dlna_src->trick_mode == GST_DLNA_MODE_RANGE && dlna_src->is_encrypted) { g_snprintf (range_dtcp_field_value, 64, "%s%" G_GUINT64_FORMAT "-", - range_dtcp_field_value_prefix, start); + range_dtcp_field_value_prefix, dlna_src->requested_segment.start); gst_structure_set (extra_headers_struct, range_dtcp_field_name, G_TYPE_STRING, &range_dtcp_field_value, NULL); @@ -1444,17 +1430,12 @@ dlna_src_adjust_http_src_headers (GstDlnaSrc * dlna_src, gfloat rate, } else GST_INFO_OBJECT (dlna_src, "Not adjusting with range.dtcp.com"); - g_value_init (&struct_value, GST_TYPE_STRUCTURE); - gst_value_set_structure (&struct_value, extra_headers_struct); - g_object_set_property (G_OBJECT (dlna_src->http_src), "extra-headers", - &struct_value); + g_object_set (dlna_src->http_src, "extra-headers", extra_headers_struct, NULL); gst_structure_free (extra_headers_struct); /* Disable range header if necessary */ - if (disable_range_header || !dlna_src->byte_seek_supported) { - g_value_set_boolean (&boolean_value, TRUE); - g_object_set_property (G_OBJECT (dlna_src->http_src), - "exclude-range-header", &boolean_value); + if (disable_range_header) { + g_object_set (dlna_src->http_src, "exclude-range-header", TRUE, NULL); GST_INFO_OBJECT (dlna_src, "Adjust headers by excluding range header, flag: %d, supported: %d", disable_range_header, dlna_src->byte_seek_supported); @@ -1560,6 +1541,72 @@ dlna_src_uri_init (GstDlnaSrc * dlna_src, GError ** error) return TRUE; } +static GstPadProbeReturn +dlna_src_outgoing_probe(GstPad *pad, GstPadProbeInfo *info, GstDlnaSrc *dlna_src) +{ + GstEvent *event; + + event = GST_PAD_PROBE_INFO_EVENT (info); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEGMENT: + { + const GstSegment *segment; + GstEvent *new_event; + GstSegment new_segment; + + GST_DEBUG_OBJECT (dlna_src, "Saw outgoing segment %"GST_PTR_FORMAT, event); + gst_event_parse_segment(event, &segment); + gst_segment_copy_into ((const GstSegment*) &dlna_src->requested_segment, &new_segment); + + /* FIXME : *actually* replace with what the server returns (i.e. the response headers in the + * custom downstream sticky event below) */ + switch (dlna_src->trick_mode) { + case GST_DLNA_MODE_RANGE: + /* Nothing to modify, we normally sent the full seek request */ + break; + case GST_DLNA_MODE_TIMERANGE: + { + GST_DEBUG_OBJECT (dlna_src, "TIMERANGE MODE"); + new_segment.time = new_segment.start; + new_event = gst_event_new_segment ((const GstSegment*) &new_segment); + gst_event_set_seqnum (new_event, dlna_src->seek_seqnum); + gst_event_unref (event); + GST_DEBUG_OBJECT (dlna_src, "Replacing with event %"GST_PTR_FORMAT, new_event); + GST_PAD_PROBE_INFO_DATA (info) = new_event; + } + break; + case GST_DLNA_MODE_PLAYSPEED: + GST_DEBUG_OBJECT (dlna_src, "PLAYSPEED MODE"); + /* Switch rate and applied_rate (server modified the stream) */ + new_segment.time = new_segment.start; + new_segment.applied_rate = new_segment.rate; + new_segment.rate = 1.0; + new_event = gst_event_new_segment ((const GstSegment*) &new_segment); + gst_event_set_seqnum (new_event, dlna_src->seek_seqnum); + gst_event_unref (event); + GST_DEBUG_OBJECT (dlna_src, "Replacing with event %"GST_PTR_FORMAT, new_event); + GST_PAD_PROBE_INFO_DATA (info) = new_event; + break; + default: + break; + } + break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM_STICKY: + { + if (gst_event_has_name (event, "http-headers")) { + GST_DEBUG_OBJECT (dlna_src, "Received request/response headers from souphttpsrc: %"GST_PTR_FORMAT, event); + } + } + default: + GST_DEBUG_OBJECT (dlna_src, "Saw event %"GST_PTR_FORMAT, event); + break; + } + + return GST_PAD_PROBE_OK; +} + /** * Perform actions necessary based on supplied URI which is called by * playbin when this element is selected as source. @@ -1616,6 +1663,10 @@ dlna_src_setup_bin (GstDlnaSrc * dlna_src, GError ** error) return FALSE; } + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) dlna_src_outgoing_probe, + dlna_src, NULL); + GST_DEBUG_OBJECT (dlna_src, "Got src pad to use for ghostpad of dlnasrc bin"); dlna_src->src_pad = gst_ghost_pad_new ("src", pad); gst_pad_set_active (dlna_src->src_pad, TRUE); @@ -1641,6 +1692,7 @@ dlna_src_setup_bin (GstDlnaSrc * dlna_src, GError ** error) return TRUE; } + /** * Setup dtcp decoder element and add to src in order to handle DTCP encrypted * content diff --git a/src/gstdlnasrc.h b/src/gstdlnasrc.h index e414e6f..30f832d 100644 --- a/src/gstdlnasrc.h +++ b/src/gstdlnasrc.h @@ -44,6 +44,14 @@ G_BEGIN_DECLS #define PLAYSPEEDS_MAX_CNT 64 +/* Seek modes used */ +typedef enum { + GST_DLNA_MODE_NONE = 1 << 0, + GST_DLNA_MODE_PLAYSPEED = 1 << 1, + GST_DLNA_MODE_TIMERANGE = 1 << 2, + GST_DLNA_MODE_RANGE = 1 << 3 +} GstDlnaTrickMode; + typedef struct _GstDlnaSrc GstDlnaSrc; typedef struct _GstDlnaSrcClass GstDlnaSrcClass; @@ -61,6 +69,7 @@ struct _GstDlnaSrc guint dtcp_blocksize; gchar* dtcp_key_storage; + /* Current URI */ gchar *uri; SoupSession *soup_session; @@ -68,15 +77,19 @@ struct _GstDlnaSrc GstDlnaSrcHeadResponse* server_info; - gfloat rate; - gfloat requested_rate; - GstFormat requested_format; - guint64 requested_start; - guint64 requested_stop; + /* Requested seek values from downstream */ + GstSegment requested_segment; + /* Sequence number from upstream seek event to be used + * for any following events */ + guint32 seek_seqnum; + /* Trick mode system used */ + GstDlnaTrickMode trick_mode; + + /* Active segment (might be different from requested segment) + * In sync with current SEGMENT event outputted */ + GstSegment current_segment; - guint32 time_seek_seqnum; guint64 time_seek_event_start; - gboolean handled_time_seek_seqnum; gboolean is_uri_initialized; gboolean is_live; |