diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c index bf9e29bbc0..c7fefa7ad3 100644 --- a/ext/hls/gsthlsdemux.c +++ b/ext/hls/gsthlsdemux.c @@ -122,6 +122,8 @@ static GstFlowReturn gst_hls_demux_update_fragment_info (GstAdaptiveDemuxStream static gboolean gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, guint64 bitrate); static void gst_hls_demux_reset (GstAdaptiveDemux * demux); +static gboolean gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux, + gint64 * start, gint64 * stop); #define gst_hls_demux_parent_class parent_class G_DEFINE_TYPE (GstHLSDemux, gst_hls_demux, GST_TYPE_ADAPTIVE_DEMUX); @@ -195,6 +197,7 @@ gst_hls_demux_class_init (GstHLSDemuxClass * klass) "Andoni Morales Alastruey "); adaptivedemux_class->is_live = gst_hls_demux_is_live; + adaptivedemux_class->get_live_seek_range = gst_hls_demux_get_live_seek_range; adaptivedemux_class->get_duration = gst_hls_demux_get_duration; adaptivedemux_class->get_manifest_update_interval = gst_hls_demux_get_manifest_update_interval; @@ -1295,3 +1298,12 @@ gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux * demux) return gst_util_uint64_scale (gst_m3u8_client_get_target_duration (hlsdemux->client), G_USEC_PER_SEC, GST_SECOND); } + +static gboolean +gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start, + gint64 * stop) +{ + GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux); + + return gst_m3u8_client_get_seek_range (hlsdemux->client, start, stop); +} diff --git a/ext/hls/m3u8.c b/ext/hls/m3u8.c index c737b3d4a6..ee0a45d849 100755 --- a/ext/hls/m3u8.c +++ b/ext/hls/m3u8.c @@ -51,8 +51,8 @@ gst_g_list_copy_deep (GList * list, GCopyFunc func, gpointer user_data) static GstM3U8 *gst_m3u8_new (void); static void gst_m3u8_free (GstM3U8 * m3u8); -static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data, - gboolean * updated); +static gboolean gst_m3u8_update (GstM3U8Client * client, GstM3U8 * m3u8, + gchar * data, gboolean * updated); static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration, guint sequence); static void gst_m3u8_media_file_free (GstM3U8MediaFile * self); @@ -382,7 +382,8 @@ gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b) * @data: a m3u8 playlist text data, taking ownership */ static gboolean -gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) +gst_m3u8_update (GstM3U8Client * client, GstM3U8 * self, gchar * data, + gboolean * updated) { gint val; GstClockTime duration; @@ -723,6 +724,37 @@ gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated) self->current_variant = g_list_find_custom (self->lists, top_variant_uri, (GCompareFunc) _m3u8_compare_uri); } + /* calculate the start and end times of this media playlist. */ + if (self->files) { + GList *walk; + GstM3U8MediaFile *file; + GstClockTime duration = 0; + + for (walk = self->files; walk; walk = walk->next) { + file = walk->data; + duration += file->duration; + if (file->sequence > client->highest_sequence_number) { + if (client->highest_sequence_number >= 0) { + /* if an update of the media playlist has been missed, there + will be a gap between self->highest_sequence_number and the + first sequence number in this media playlist. In this situation + assume that the missing fragments had a duration of + targetduration each */ + client->last_file_end += + (file->sequence - client->highest_sequence_number - + 1) * self->targetduration; + } + client->last_file_end += file->duration; + client->highest_sequence_number = file->sequence; + } + } + if (GST_M3U8_CLIENT_IS_LIVE (client)) { + client->first_file_start = client->last_file_end - duration; + GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %" + GST_TIME_FORMAT, GST_TIME_ARGS (client->first_file_start), + GST_TIME_ARGS (client->last_file_end)); + } + } return TRUE; } @@ -740,6 +772,7 @@ gst_m3u8_client_new (const gchar * uri, const gchar * base_uri) client->sequence = -1; client->sequence_position = 0; client->update_failed_count = 0; + client->highest_sequence_number = -1; g_mutex_init (&client->lock); gst_m3u8_set_uri (client->main, g_strdup (uri), g_strdup (base_uri), NULL); @@ -781,7 +814,7 @@ gst_m3u8_client_update (GstM3U8Client * self, gchar * data) GST_M3U8_CLIENT_LOCK (self); m3u8 = self->current ? self->current : self->main; - if (!gst_m3u8_update (m3u8, data, &updated)) + if (!gst_m3u8_update (self, m3u8, data, &updated)) goto out; if (!updated) { @@ -1228,3 +1261,43 @@ gst_m3u8_client_get_current_fragment_duration (GstM3U8Client * client) GST_M3U8_CLIENT_UNLOCK (client); return dur; } + +gboolean +gst_m3u8_client_get_seek_range (GstM3U8Client * client, gint64 * start, + gint64 * stop) +{ + GstClockTime duration = 0; + GList *walk; + GstM3U8MediaFile *file; + guint count; + + g_return_val_if_fail (client != NULL, FALSE); + + GST_M3U8_CLIENT_LOCK (client); + + if (client->current == NULL || client->current->files == NULL) { + GST_M3U8_CLIENT_UNLOCK (client); + return FALSE; + } + + count = g_list_length (client->current->files); + + /* count is used to make sure the seek range is never closer than + GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of the + playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */ + for (walk = client->current->files; + walk && count >= GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE; walk = walk->next) { + file = walk->data; + --count; + duration += file->duration; + } + + if (duration <= 0) { + GST_M3U8_CLIENT_UNLOCK (client); + return FALSE; + } + *start = client->first_file_start; + *stop = *start + duration; + GST_M3U8_CLIENT_UNLOCK (client); + return TRUE; +} diff --git a/ext/hls/m3u8.h b/ext/hls/m3u8.h index 77b42d5189..cef3bc63d3 100755 --- a/ext/hls/m3u8.h +++ b/ext/hls/m3u8.h @@ -36,6 +36,11 @@ typedef struct _GstM3U8Client GstM3U8Client; #define GST_M3U8_CLIENT_UNLOCK(c) g_mutex_unlock (&c->lock); #define GST_M3U8_CLIENT_IS_LIVE(c) ((!(c)->current || (c)->current->endlist) ? FALSE : TRUE) +/* hlsdemux must not get closer to the end of a live stream than + GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments. Section 6.3.3 + "Playing the Playlist file" of the HLS draft states that this + value is three fragments */ +#define GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE 3 struct _GstM3U8 { @@ -86,6 +91,9 @@ struct _GstM3U8Client guint update_failed_count; gint64 sequence; /* the next sequence for this client */ GstClockTime sequence_position; /* position of this sequence */ + gint64 highest_sequence_number; /* largest seen sequence number */ + GstClockTime first_file_start; /* timecode of the start of the first fragment in the current media playlist */ + GstClockTime last_file_end; /* timecode of the end of the last fragment in the current media playlist */ GMutex lock; }; @@ -112,5 +120,7 @@ GList * gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint64 gst_m3u8_client_get_current_fragment_duration (GstM3U8Client * client); +gboolean gst_m3u8_client_get_seek_range(GstM3U8Client * client, gint64 * start, gint64 * stop); + G_END_DECLS #endif /* __M3U8_H__ */