hlsdemux: Implement live seeking

hlsdemux assumes that seeking is not allowed for live streams,
however seek is possible if there are sufficient fragments in the
manifest. For example the BBC have live streams that contain 2 hours
of fragments.

The seek code for both live and on-demand is common code. The
difference between them is that an offset has to be calculated
for the timecode of the first fragment in the live playlist.

When hlsdemux starts to play a live stream, the possible seek range
is between 0 and A seconds. After some time has passed, the beginning of
the stream will no longer be available in the playlist and the seek
range is between B and C seconds.

Seek range:
start          0 ........... A
later               B ........... C

This commit adds code to keep a note of the B and C values
and the highest sequence number it has seen. Every time it updates the
media playlist, it walks the list of fragments, seeing if there is a
fragment with sequence number > highest_seen_sequence. If so, the values
of B and C are updated. The value of B is used when timestamping
buffers.

It also makes sure the seek range is never closer than three fragments
from the end of the playlist - see 6.3.3. "Playing the Playlist file"
of the HLS draft.

https://bugzilla.gnome.org/show_bug.cgi?id=725435
This commit is contained in:
Alex Ashley 2014-04-04 16:45:51 +01:00 committed by Thiago Santos
parent af78e2501c
commit 50b5d94b2a
3 changed files with 99 additions and 4 deletions

View file

@ -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 <ylatuya@gmail.com>");
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);
}

View file

@ -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;
}

View file

@ -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__ */