diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c index 78fc828084..2ce7c27422 100644 --- a/ext/hls/gsthlsdemux.c +++ b/ext/hls/gsthlsdemux.c @@ -114,6 +114,9 @@ static gboolean gst_hls_demux_set_location (GstHLSDemux * demux, const gchar * uri); static gchar *gst_hls_src_buf_to_utf8_playlist (GstBuffer * buf); +static gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux, + guint max_bitrate); + #define gst_hls_demux_parent_class parent_class G_DEFINE_TYPE (GstHLSDemux, gst_hls_demux, GST_TYPE_ELEMENT); @@ -332,7 +335,7 @@ gst_hls_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) gint64 start, stop; GList *walk; GstClockTime current_pos, target_pos; - gint current_sequence; + gint64 current_sequence; GstM3U8MediaFile *file; GST_INFO_OBJECT (demux, "Received GST_EVENT_SEEK"); @@ -351,15 +354,67 @@ gst_hls_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) return FALSE; } + if ((rate > 1.0 || rate < -1.0) && (!demux->client->main + || !demux->client->main->iframe_lists)) { + GST_ERROR_OBJECT (demux, + "Trick modes only allowed for streams with I-frame lists"); + gst_event_unref (event); + return FALSE; + } + GST_DEBUG_OBJECT (demux, "seek event, rate: %f start: %" GST_TIME_FORMAT " stop: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (start), GST_TIME_ARGS (stop)); + gst_hls_demux_pause_tasks (demux); + + /* wait for streaming to finish */ + g_rec_mutex_lock (&demux->updates_lock); + g_rec_mutex_unlock (&demux->updates_lock); + + g_rec_mutex_lock (&demux->stream_lock); + + /* Use I-frame variants for trick modes */ + if ((rate > 1.0 || rate < -1.0) && demux->segment.rate >= -1.0 + && demux->segment.rate <= 1.0) { + GST_M3U8_CLIENT_LOCK (demux->client); + /* Switch to I-frame variant */ + demux->client->main->current_variant = + demux->client->main->iframe_lists; + GST_M3U8_CLIENT_UNLOCK (demux->client); + gst_m3u8_client_set_current (demux->client, + demux->client->main->iframe_lists->data); + + gst_uri_downloader_reset (demux->downloader); + gst_hls_demux_update_playlist (demux, FALSE, NULL); + demux->discont = TRUE; + demux->do_typefind = TRUE; + + gst_hls_demux_change_playlist (demux, + demux->current_download_rate * demux->bitrate_limit / ABS (rate)); + } else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0 + || demux->segment.rate > 1.0)) { + GST_M3U8_CLIENT_LOCK (demux->client); + /* Switch to normal variant */ + demux->client->main->current_variant = demux->client->main->lists; + GST_M3U8_CLIENT_UNLOCK (demux->client); + gst_m3u8_client_set_current (demux->client, + demux->client->main->lists->data); + + gst_uri_downloader_reset (demux->downloader); + gst_hls_demux_update_playlist (demux, FALSE, NULL); + demux->discont = TRUE; + demux->do_typefind = TRUE; + + gst_hls_demux_change_playlist (demux, + demux->current_download_rate * demux->bitrate_limit); + } + GST_M3U8_CLIENT_LOCK (demux->client); file = GST_M3U8_MEDIA_FILE (demux->client->current->files->data); current_sequence = file->sequence; current_pos = 0; - target_pos = (GstClockTime) start; + target_pos = rate > 0 ? start : stop; /* FIXME: Here we need proper discont handling */ for (walk = demux->client->current->files; walk; walk = walk->next) { file = walk->data; @@ -383,16 +438,9 @@ gst_hls_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) gst_pad_push_event (demux->srcpad, gst_event_new_flush_start ()); } - gst_hls_demux_pause_tasks (demux); - - /* wait for streaming to finish */ - g_rec_mutex_lock (&demux->updates_lock); - g_rec_mutex_unlock (&demux->updates_lock); - - g_rec_mutex_lock (&demux->stream_lock); - GST_M3U8_CLIENT_LOCK (demux->client); - GST_DEBUG_OBJECT (demux, "seeking to sequence %d", current_sequence); + GST_DEBUG_OBJECT (demux, "seeking to sequence %u", + (guint) current_sequence); demux->client->sequence = current_sequence; demux->client->sequence_position = current_pos; GST_M3U8_CLIENT_UNLOCK (demux->client); @@ -768,7 +816,7 @@ gst_hls_demux_stream_loop (GstHLSDemux * demux) /* Got a new fragment or not live anymore? */ if (gst_m3u8_client_get_next_fragment (demux->client, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL) + NULL, NULL, NULL, NULL, NULL, NULL, demux->segment.rate > 0) || !gst_m3u8_client_is_live (demux->client)) break; @@ -809,7 +857,7 @@ gst_hls_demux_stream_loop (GstHLSDemux * demux) } } else { demux->download_failed_count = 0; - gst_m3u8_client_advance_fragment (demux->client); + gst_m3u8_client_advance_fragment (demux->client, demux->segment.rate > 0); if (demux->stop_updates_task) { g_object_unref (fragment); @@ -1135,7 +1183,7 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update, * three fragments before the end of the list */ if (updated && update == FALSE && demux->client->current && gst_m3u8_client_is_live (demux->client)) { - guint last_sequence; + gint64 last_sequence; GST_M3U8_CLIENT_LOCK (demux->client); last_sequence = @@ -1143,8 +1191,8 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update, data)->sequence; if (demux->client->sequence >= last_sequence - 3) { - GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %d", - last_sequence - 3); + GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %u", + (guint) (last_sequence - 3)); demux->need_segment = TRUE; demux->client->sequence = last_sequence - 3; } @@ -1211,7 +1259,11 @@ retry_failover_protection: GST_M3U8_CLIENT_UNLOCK (demux->client); gst_m3u8_client_set_current (demux->client, previous_variant->data); /* Try a lower bitrate (or stop if we just tried the lowest) */ - if (new_bandwidth == + if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == + GST_M3U8 (g_list_first (demux->client->main->iframe_lists)-> + data)->bandwidth) + return FALSE; + else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth == GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth) return FALSE; else @@ -1421,7 +1473,7 @@ gst_hls_demux_get_next_fragment (GstHLSDemux * demux, *end_of_playlist = FALSE; if (!gst_m3u8_client_get_next_fragment (demux->client, &discont, &next_fragment_uri, &duration, ×tamp, &range_start, &range_end, - &key, &iv)) { + &key, &iv, demux->segment.rate > 0)) { GST_INFO_OBJECT (demux, "This playlist doesn't contain more fragments"); *end_of_playlist = TRUE; return NULL; diff --git a/ext/hls/m3u8.c b/ext/hls/m3u8.c index 3ef7e9088e..3083adcbd6 100644 --- a/ext/hls/m3u8.c +++ b/ext/hls/m3u8.c @@ -558,7 +558,7 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) data = g_utf8_next_char (end); /* skip \n */ } - /* redorder playlists by bitrate */ + /* reorder playlists by bitrate */ if (self->lists) { gchar *top_variant_uri = NULL; gboolean iframe = FALSE; @@ -670,7 +670,7 @@ gst_m3u8_client_update (GstM3U8Client * self, gchar * data) self->sequence = GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence; self->sequence_position = 0; - GST_DEBUG ("Setting first sequence at %d", self->sequence); + GST_DEBUG ("Setting first sequence at %u", (guint) self->sequence); } ret = TRUE; @@ -688,17 +688,26 @@ _find_current (GstM3U8MediaFile * file, GstM3U8Client * client) static gboolean _find_next (GstM3U8MediaFile * file, GstM3U8Client * client) { - GST_DEBUG ("Found fragment %d", file->sequence); + GST_DEBUG ("Found fragment %u", (guint) file->sequence); if (file->sequence >= client->sequence) return FALSE; return TRUE; } +static gboolean +_find_previous (GstM3U8MediaFile * file, GstM3U8Client * client) +{ + GST_DEBUG ("Found fragment %u", (guint) file->sequence); + if (file->sequence <= client->sequence) + return FALSE; + return TRUE; +} + gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client, gboolean * discontinuity, const gchar ** uri, GstClockTime * duration, GstClockTime * timestamp, gint64 * range_start, gint64 * range_end, - const gchar ** key, const guint8 ** iv) + const gchar ** key, const guint8 ** iv, gboolean forward) { GList *l; GstM3U8MediaFile *file; @@ -707,9 +716,13 @@ gst_m3u8_client_get_next_fragment (GstM3U8Client * client, g_return_val_if_fail (client->current != NULL, FALSE); GST_M3U8_CLIENT_LOCK (client); - GST_DEBUG ("Looking for fragment %d", client->sequence); + GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, client->sequence); + if (client->sequence < 0) { + GST_M3U8_CLIENT_UNLOCK (client); + return FALSE; + } l = g_list_find_custom (client->current->files, client, - (GCompareFunc) _find_next); + (GCompareFunc) (forward ? _find_next : _find_previous)); if (l == NULL) { GST_M3U8_CLIENT_UNLOCK (client); return FALSE; @@ -717,7 +730,7 @@ gst_m3u8_client_get_next_fragment (GstM3U8Client * client, file = GST_M3U8_MEDIA_FILE (l->data); GST_DEBUG ("Got fragment with sequence %u (client sequence %u)", - file->sequence, client->sequence); + (guint) file->sequence, (guint) client->sequence); if (timestamp) *timestamp = client->sequence_position; @@ -742,7 +755,7 @@ gst_m3u8_client_get_next_fragment (GstM3U8Client * client, } void -gst_m3u8_client_advance_fragment (GstM3U8Client * client) +gst_m3u8_client_advance_fragment (GstM3U8Client * client, gboolean forward) { GList *l; GstM3U8MediaFile *file; @@ -751,9 +764,9 @@ gst_m3u8_client_advance_fragment (GstM3U8Client * client) g_return_if_fail (client->current != NULL); GST_M3U8_CLIENT_LOCK (client); - GST_DEBUG ("Looking for fragment %d", client->sequence); + GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, client->sequence); l = g_list_find_custom (client->current->files, client, - (GCompareFunc) _find_next); + (GCompareFunc) _find_current); if (l == NULL) { GST_ERROR ("Could not find current fragment"); GST_M3U8_CLIENT_UNLOCK (client); @@ -761,9 +774,17 @@ gst_m3u8_client_advance_fragment (GstM3U8Client * client) } file = GST_M3U8_MEDIA_FILE (l->data); - GST_DEBUG ("Advancing from sequence %u", file->sequence); - client->sequence = file->sequence + 1; - client->sequence_position += file->duration; + GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence); + if (forward) { + client->sequence = file->sequence + 1; + client->sequence_position += file->duration; + } else { + client->sequence = file->sequence - 1; + if (client->sequence_position > file->duration) + client->sequence_position -= file->duration; + else + client->sequence_position = 0; + } GST_M3U8_CLIENT_UNLOCK (client); } diff --git a/ext/hls/m3u8.h b/ext/hls/m3u8.h index 03a9a65a62..599ab4463c 100644 --- a/ext/hls/m3u8.h +++ b/ext/hls/m3u8.h @@ -59,7 +59,7 @@ struct _GstM3U8 GList *iframe_lists; /* I-frame lists from the main playlist */ GList *current_variant; /* Current variant playlist used */ GstM3U8 *parent; /* main playlist (if any) */ - guint mediasequence; /* EXT-X-MEDIA-SEQUENCE & increased with new media file */ + gint64 mediasequence; /* EXT-X-MEDIA-SEQUENCE & increased with new media file */ }; struct _GstM3U8MediaFile @@ -67,7 +67,7 @@ struct _GstM3U8MediaFile gchar *title; GstClockTime duration; gchar *uri; - guint sequence; /* the sequence nb of this file */ + gint64 sequence; /* the sequence nb of this file */ gboolean discont; /* this file marks a discontinuity */ gchar *key; guint8 iv[16]; @@ -79,7 +79,7 @@ struct _GstM3U8Client GstM3U8 *main; /* main playlist */ GstM3U8 *current; guint update_failed_count; - gint sequence; /* the next sequence for this client */ + gint64 sequence; /* the next sequence for this client */ GstClockTime sequence_position; /* position of this sequence */ GMutex lock; }; @@ -92,8 +92,8 @@ void gst_m3u8_client_set_current (GstM3U8Client * client, GstM3U8 * m3u8); gboolean gst_m3u8_client_get_next_fragment (GstM3U8Client * client, gboolean * discontinuity, const gchar ** uri, GstClockTime * duration, GstClockTime * timestamp, gint64 * range_start, gint64 * range_end, - const gchar ** key, const guint8 ** iv); -void gst_m3u8_client_advance_fragment (GstM3U8Client * client); + const gchar ** key, const guint8 ** iv, gboolean forward); +void gst_m3u8_client_advance_fragment (GstM3U8Client * client, gboolean forward); GstClockTime gst_m3u8_client_get_duration (GstM3U8Client * client); GstClockTime gst_m3u8_client_get_target_duration (GstM3U8Client * client); const gchar *gst_m3u8_client_get_uri(GstM3U8Client * client);