hlsdemux2: Implement LL-HLS flag and part-hold-back/hold-back in live.

Add a flag to hlsdemux to enable or disable LL-HLS handling.

When LL-HLS is enabled and an LL-HLS playlist is loaded, use the part-hold-back
threshold to choose a starting segment.

For live streams that aren't LL-HLS, use the provided hold-back attribute, or
fall back to landing 3 segments from the end.

Make the gst_hls_media_playlist_seek() method able to choose a partial segment
within 2 target durations of the end of the playlist when requested.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3883>
This commit is contained in:
Jan Schmidt 2022-08-13 05:44:24 +10:00 committed by GStreamer Marge Bot
parent 9848c1a1a1
commit 43e042c4b7
5 changed files with 293 additions and 65 deletions

View File

@ -67,9 +67,11 @@ enum
PROP_0, PROP_0,
PROP_START_BITRATE, PROP_START_BITRATE,
PROP_LLHLS_ENABLED,
}; };
#define DEFAULT_START_BITRATE 0 #define DEFAULT_START_BITRATE 0
#define DEFAULT_LLHLS_ENABLED TRUE
/* Maximum values for mpeg-ts DTS values */ /* Maximum values for mpeg-ts DTS values */
#define MPEG_TS_MAX_PTS (((((guint64)1) << 33) * (guint64)100000) / 9) #define MPEG_TS_MAX_PTS (((((guint64)1) << 33) * (guint64)100000) / 9)
@ -233,6 +235,9 @@ gst_hls_demux_set_property (GObject * object, guint prop_id,
case PROP_START_BITRATE: case PROP_START_BITRATE:
demux->start_bitrate = g_value_get_uint (value); demux->start_bitrate = g_value_get_uint (value);
break; break;
case PROP_LLHLS_ENABLED:
demux->llhls_enabled = g_value_get_boolean (value);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -249,6 +254,9 @@ gst_hls_demux_get_property (GObject * object, guint prop_id,
case PROP_START_BITRATE: case PROP_START_BITRATE:
g_value_set_uint (value, demux->start_bitrate); g_value_set_uint (value, demux->start_bitrate);
break; break;
case PROP_LLHLS_ENABLED:
g_value_set_boolean (value, demux->llhls_enabled);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -277,6 +285,11 @@ gst_hls_demux2_class_init (GstHLSDemux2Class * klass)
0, G_MAXUINT, DEFAULT_START_BITRATE, 0, G_MAXUINT, DEFAULT_START_BITRATE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_LLHLS_ENABLED,
g_param_spec_boolean ("llhls-enabled", "Enable LL-HLS support",
"Enable support for LL-HLS (Low Latency HLS) downloads",
DEFAULT_LLHLS_ENABLED, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_demux_change_state); element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_demux_change_state);
gst_element_class_add_static_pad_template (element_class, &sinktemplate); gst_element_class_add_static_pad_template (element_class, &sinktemplate);
@ -302,6 +315,7 @@ gst_hls_demux2_class_init (GstHLSDemux2Class * klass)
static void static void
gst_hls_demux2_init (GstHLSDemux * demux) gst_hls_demux2_init (GstHLSDemux * demux)
{ {
demux->llhls_enabled = DEFAULT_LLHLS_ENABLED;
demux->keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); demux->keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
g_mutex_init (&demux->keys_lock); g_mutex_init (&demux->keys_lock);
} }
@ -497,7 +511,6 @@ gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
GstFlowReturn ret = GST_FLOW_OK; GstFlowReturn ret = GST_FLOW_OK;
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux; GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GstM3U8MediaSegment *new_position;
GST_DEBUG_OBJECT (stream, GST_DEBUG_OBJECT (stream,
"is_variant:%d media:%p current_variant:%p forward:%d ts:%" "is_variant:%d media:%p current_variant:%p forward:%d ts:%"
@ -514,17 +527,22 @@ gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
} }
} }
/* FIXME: Allow jumping to partial segments in LL-HLS? */ /* Allow jumping to partial segments in the last 2 segments in LL-HLS */
new_position = if (hls_stream->llhls_enabled)
gst_hls_media_playlist_seek (hls_stream->playlist, forward, flags, ts); flags |= GST_HLS_M3U8_SEEK_FLAG_ALLOW_PARTIAL;
if (new_position) {
GstM3U8SeekResult seek_result;
if (gst_hls_media_playlist_seek (hls_stream->playlist, forward, flags, ts,
&seek_result)) {
if (hls_stream->current_segment) if (hls_stream->current_segment)
gst_m3u8_media_segment_unref (hls_stream->current_segment); gst_m3u8_media_segment_unref (hls_stream->current_segment);
hls_stream->current_segment = new_position; hls_stream->current_segment = seek_result.segment;
hls_stream->in_partial_segments = FALSE; hls_stream->in_partial_segments = seek_result.found_partial_segment;
hls_stream->part_idx = seek_result.part_idx;
hls_stream->reset_pts = TRUE; hls_stream->reset_pts = TRUE;
if (final_ts) if (final_ts)
*final_ts = new_position->stream_time; *final_ts = seek_result.stream_time;
} else { } else {
GST_WARNING_OBJECT (stream, "Seeking failed"); GST_WARNING_OBJECT (stream, "Seeking failed");
ret = GST_FLOW_ERROR; ret = GST_FLOW_ERROR;
@ -547,6 +565,8 @@ create_common_hls_stream (GstHLSDemux * demux, const gchar * name)
GstAdaptiveDemux2Stream *stream; GstAdaptiveDemux2Stream *stream;
stream = g_object_new (GST_TYPE_HLS_DEMUX_STREAM, "name", name, NULL); stream = g_object_new (GST_TYPE_HLS_DEMUX_STREAM, "name", name, NULL);
GST_HLS_DEMUX_STREAM (stream)->llhls_enabled = demux->llhls_enabled;
gst_adaptive_demux2_add_stream ((GstAdaptiveDemux *) demux, stream); gst_adaptive_demux2_add_stream ((GstAdaptiveDemux *) demux, stream);
return stream; return stream;
@ -943,9 +963,21 @@ gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf)
ret = gst_hls_demux_setup_streams (demux); ret = gst_hls_demux_setup_streams (demux);
if (simple_media_playlist) { if (simple_media_playlist) {
GstM3U8SeekResult seek_result;
hlsdemux->main_stream->playlist = simple_media_playlist; hlsdemux->main_stream->playlist = simple_media_playlist;
hlsdemux->main_stream->current_segment =
gst_hls_media_playlist_get_starting_segment (simple_media_playlist); if (!gst_hls_media_playlist_get_starting_segment (simple_media_playlist,
hlsdemux->main_stream->llhls_enabled, &seek_result)) {
GST_DEBUG_OBJECT (hlsdemux->main_stream,
"Failed to find a segment to start at");
return FALSE;
}
hlsdemux->main_stream->current_segment = seek_result.segment;
hlsdemux->main_stream->in_partial_segments =
seek_result.found_partial_segment;
hlsdemux->main_stream->part_idx = seek_result.part_idx;
setup_initial_playlist (hlsdemux, simple_media_playlist); setup_initial_playlist (hlsdemux, simple_media_playlist);
gst_hls_update_time_mappings (hlsdemux, simple_media_playlist); gst_hls_update_time_mappings (hlsdemux, simple_media_playlist);
gst_hls_media_playlist_dump (simple_media_playlist); gst_hls_media_playlist_dump (simple_media_playlist);
@ -1687,6 +1719,7 @@ gst_hls_demux_stream_finish_fragment (GstAdaptiveDemux2Stream * stream)
if (hls_stream->current_segment == NULL) { if (hls_stream->current_segment == NULL) {
/* We can't advance, we just return OK for now and let the base class /* We can't advance, we just return OK for now and let the base class
* trigger a new download (or fail and resync itself) */ * trigger a new download (or fail and resync itself) */
GST_DEBUG_OBJECT (stream, "Can't advance - current_segment is NULL");
return GST_FLOW_OK; return GST_FLOW_OK;
} }
@ -1905,7 +1938,7 @@ gst_hls_demux_stream_advance_fragment (GstAdaptiveDemux2Stream * stream)
new_segment = new_segment =
gst_hls_media_playlist_advance_fragment (hlsdemux_stream->playlist, gst_hls_media_playlist_advance_fragment (hlsdemux_stream->playlist,
hlsdemux_stream->current_segment, stream->demux->segment.rate > 0, hlsdemux_stream->current_segment, stream->demux->segment.rate > 0,
TRUE /* FIXME: Only in LL-HLS mode */ ); hlsdemux_stream->llhls_enabled);
if (new_segment) { if (new_segment) {
hlsdemux_stream->reset_pts = FALSE; hlsdemux_stream->reset_pts = FALSE;
@ -2225,23 +2258,32 @@ gst_hls_demux_reset_for_lost_sync (GstHLSDemux * hlsdemux)
if (hls_stream->is_variant) { if (hls_stream->is_variant) {
GstHLSTimeMap *map; GstHLSTimeMap *map;
GstM3U8SeekResult seek_result;
/* Resynchronize the variant stream */ /* Resynchronize the variant stream */
g_assert (stream->current_position != GST_CLOCK_STIME_NONE); g_assert (stream->current_position != GST_CLOCK_STIME_NONE);
hls_stream->current_segment = if (gst_hls_media_playlist_get_starting_segment (hls_stream->playlist,
gst_hls_media_playlist_get_starting_segment (hls_stream->playlist); hls_stream->llhls_enabled, &seek_result)) {
hls_stream->current_segment->stream_time = stream->current_position; hls_stream->current_segment = seek_result.segment;
gst_hls_media_playlist_recalculate_stream_time (hls_stream->playlist, hls_stream->in_partial_segments = seek_result.found_partial_segment;
hls_stream->current_segment); hls_stream->part_idx = seek_result.part_idx;
GST_DEBUG_OBJECT (stream,
"Resynced variant playlist to %" GST_STIME_FORMAT, hls_stream->current_segment->stream_time = stream->current_position;
GST_STIME_ARGS (stream->current_position)); gst_hls_media_playlist_recalculate_stream_time (hls_stream->playlist,
map = hls_stream->current_segment);
gst_hls_find_time_map (hlsdemux, GST_DEBUG_OBJECT (stream,
hls_stream->current_segment->discont_sequence); "Resynced variant playlist to %" GST_STIME_FORMAT,
if (map) GST_STIME_ARGS (stream->current_position));
map->internal_time = GST_CLOCK_TIME_NONE; map =
gst_hls_update_time_mappings (hlsdemux, hls_stream->playlist); gst_hls_find_time_map (hlsdemux,
gst_hls_media_playlist_dump (hls_stream->playlist); hls_stream->current_segment->discont_sequence);
if (map)
map->internal_time = GST_CLOCK_TIME_NONE;
gst_hls_update_time_mappings (hlsdemux, hls_stream->playlist);
gst_hls_media_playlist_dump (hls_stream->playlist);
} else {
GST_ERROR_OBJECT (stream, "Failed to locate a segment to restart at!");
}
} else { } else {
/* Force playlist update for the rendition streams, it will resync to the /* Force playlist update for the rendition streams, it will resync to the
* variant stream on the next round */ * variant stream on the next round */
@ -2314,7 +2356,7 @@ gst_hls_demux_stream_update_media_playlist (GstHLSDemux * demux,
GST_STIME_ARGS (stream->current_segment->stream_time), GST_STIME_ARGS (stream->current_segment->stream_time),
GST_STR_NULL (stream->current_segment->uri)); GST_STR_NULL (stream->current_segment->uri));
/* Use best-effort techniques to find the correponding current media segment /* Use best-effort techniques to find the corresponding current media segment
* in the new playlist. This might be off in some cases, but it doesn't matter * in the new playlist. This might be off in some cases, but it doesn't matter
* since we will be checking the embedded timestamp later */ * since we will be checking the embedded timestamp later */
new_segment = new_segment =
@ -2496,18 +2538,17 @@ gst_hls_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream)
if (hlsdemux_stream->current_segment == NULL) { if (hlsdemux_stream->current_segment == NULL) {
GST_LOG_OBJECT (stream, "No current segment"); GST_LOG_OBJECT (stream, "No current segment");
if (stream->current_position == GST_CLOCK_TIME_NONE) { if (stream->current_position == GST_CLOCK_TIME_NONE) {
GST_DEBUG_OBJECT (stream, "Setting up initial segment"); GstM3U8SeekResult seek_result;
hlsdemux_stream->current_segment =
gst_hls_media_playlist_get_starting_segment
(hlsdemux_stream->playlist);
if (hlsdemux_stream->current_segment->partial_only) { GST_DEBUG_OBJECT (stream, "Setting up initial segment");
/* FIXME: We might find an independent partial segment
* that's still old enough (beyond the part_hold_back threshold) if (gst_hls_media_playlist_get_starting_segment
* but closer to the live edge than the start of the segment. This (hlsdemux_stream->playlist, hlsdemux_stream->llhls_enabled,
* check should be done inside get_starting_segment() */ &seek_result)) {
hlsdemux_stream->in_partial_segments = TRUE; hlsdemux_stream->current_segment = seek_result.segment;
hlsdemux_stream->part_idx = 0; hlsdemux_stream->in_partial_segments =
seek_result.found_partial_segment;
hlsdemux_stream->part_idx = seek_result.part_idx;
} }
} else { } else {
if (gst_hls_media_playlist_has_lost_sync (hlsdemux_stream->playlist, if (gst_hls_media_playlist_has_lost_sync (hlsdemux_stream->playlist,

View File

@ -95,6 +95,11 @@ struct _GstHLSDemuxStream
/* A stream either variants or renditions */ /* A stream either variants or renditions */
gboolean is_variant; gboolean is_variant;
/* A copy of the demuxer flag, stored when the
* stream is created, so it can't change after
* the stream starts downloading things */
gboolean llhls_enabled;
/* Rendition-specific fields */ /* Rendition-specific fields */
GstStreamType rendition_type; /* FIXME: Also used by variant streams */ GstStreamType rendition_type; /* FIXME: Also used by variant streams */
gchar *lang; gchar *lang;
@ -192,6 +197,9 @@ struct _GstHLSDemux2
/* Initial bitrate to use before any bandwidth measurement */ /* Initial bitrate to use before any bandwidth measurement */
guint start_bitrate; guint start_bitrate;
/* Whether LL-HLS (Low Latency HLS) features are enabled */
gboolean llhls_enabled;
/* Decryption key cache: url => GstHLSKey */ /* Decryption key cache: url => GstHLSKey */
GHashTable *keys; GHashTable *keys;
GMutex keys_lock; GMutex keys_lock;

View File

@ -1215,28 +1215,116 @@ gst_hls_media_playlist_has_same_data (GstHLSMediaPlaylist * self,
* segment group that disappears before we're done with it. * segment group that disappears before we're done with it.
* We want a segment or partial that contains a keyframe if possible * We want a segment or partial that contains a keyframe if possible
*/ */
GstM3U8MediaSegment * gboolean
gst_hls_media_playlist_seek (GstHLSMediaPlaylist * playlist, gboolean forward, gst_hls_media_playlist_seek (GstHLSMediaPlaylist * playlist, gboolean forward,
GstSeekFlags flags, GstClockTimeDiff ts) GstSeekFlags flags, GstClockTimeDiff ts, GstM3U8SeekResult * seek_result)
{ {
gboolean snap_nearest = gboolean snap_nearest =
(flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST; (flags & GST_SEEK_FLAG_SNAP_NEAREST) == GST_SEEK_FLAG_SNAP_NEAREST;
gboolean snap_after = gboolean snap_after =
(flags & GST_SEEK_FLAG_SNAP_AFTER) == GST_SEEK_FLAG_SNAP_AFTER; (flags & GST_SEEK_FLAG_SNAP_AFTER) == GST_SEEK_FLAG_SNAP_AFTER;
gboolean want_keyunit = (flags & GST_SEEK_FLAG_KEY_UNIT);
guint idx; guint idx;
GstM3U8MediaSegment *res = NULL; GstM3U8MediaSegment *res = NULL;
guint res_part_idx = 0;
GstClockTime partial_window_start = GST_CLOCK_TIME_NONE;
GST_DEBUG ("ts:%" GST_STIME_FORMAT " forward:%d playlist uri: %s", GST_DEBUG ("target ts:%" GST_STIME_FORMAT " forward:%d playlist uri: %s",
GST_STIME_ARGS (ts), forward, playlist->uri); GST_STIME_ARGS (ts), forward, playlist->uri);
/* Can't seek if there's no segments */
if (playlist->segments->len < 1)
return FALSE;
/* Calculate the threshold at which we might start inspecting partial segments */
if (flags & GST_HLS_M3U8_SEEK_FLAG_ALLOW_PARTIAL) {
GstM3U8MediaSegment *last_seg =
g_ptr_array_index (playlist->segments, playlist->segments->len - 1);
GstClockTime playlist_end = last_seg->stream_time + last_seg->duration;
if (playlist_end >= 2 * playlist->targetduration)
partial_window_start = playlist_end - 2 * playlist->targetduration;
else
partial_window_start = last_seg->stream_time;
GST_DEBUG ("Partial segment threshold %" GST_TIME_FORMAT,
GST_TIME_ARGS (partial_window_start));
}
for (idx = 0; idx < playlist->segments->len; idx++) { for (idx = 0; idx < playlist->segments->len; idx++) {
GstM3U8MediaSegment *cand = g_ptr_array_index (playlist->segments, idx); GstM3U8MediaSegment *cand = g_ptr_array_index (playlist->segments, idx);
/* If only full segments was request, skip any segment that if (flags & GST_HLS_M3U8_SEEK_FLAG_ALLOW_PARTIAL &&
* only has EXT-X-PARTs attached */ GST_CLOCK_TIME_IS_VALID (partial_window_start) &&
if (cand->partial_only && !(flags & GST_HLS_M3U8_SEEK_FLAG_ALLOW_PARTIAL)) cand->stream_time + cand->duration > partial_window_start) {
continue; /* Permitted to land at a partial segment, but only do so if
* they are in the last 2 target durations of the playlist, so we can
* be fairly sure we'll have to time download them all before
* they get removed.
*
* 6.2.2: EXT-X-PART tags SHOULD be removed from the Playlist after they are
* greater than three Target Durations from the end of the Playlist.
* Clients MUST be able to download the Partial Segment for at least
* three Target Durations after the EXT-X-PART tag is removed from the
* Playlist.
*/
if (cand->partial_segments != NULL) {
guint part_idx;
guint last_independent_idx = 0;
for (part_idx = 0; part_idx < cand->partial_segments->len; part_idx++) {
GstM3U8PartialSegment *part =
g_ptr_array_index (cand->partial_segments, part_idx);
GST_LOG ("Inspecting partial segment sn:%" G_GINT64_FORMAT
" idx %u stream_time:%" GST_STIME_FORMAT " duration:%"
GST_TIME_FORMAT, cand->sequence, part_idx,
GST_STIME_ARGS (part->stream_time),
GST_TIME_ARGS (part->duration));
if ((forward & snap_after) || snap_nearest) {
if (!want_keyunit || part->independent) {
if (part->stream_time >= ts ||
(snap_nearest
&& (ts - part->stream_time < part->duration / 2))) {
res = cand;
res_part_idx = part_idx;
goto partial_seg_out;
}
}
} else if (!forward && snap_after) {
GstClockTime next_pos = cand->stream_time + cand->duration;
if (!want_keyunit || part->independent) {
if (next_pos <= ts && ts < next_pos + cand->duration) {
res = cand;
res_part_idx = part_idx;
goto partial_seg_out;
}
}
} else if (part->stream_time <= ts
&& ts < part->stream_time + part->duration) {
res = cand;
if (!want_keyunit || part->independent)
res_part_idx = part_idx;
else
res_part_idx = last_independent_idx;
goto partial_seg_out;
}
if (part->independent)
last_independent_idx = part_idx;
}
}
} else if (cand->partial_only) {
/* If only full segments were requested or we're still outside the partial segment
* window, skip the last segment if it only has EXT-X-PARTs attached */
continue;
}
/* For full segment alignment, we ignore the KEY_UNIT flag and assume
* all segments have a keyframe, since HLS doesn't give us reliable info
* about that */
if ((forward & snap_after) || snap_nearest) { if ((forward & snap_after) || snap_nearest) {
if (cand->stream_time >= ts || if (cand->stream_time >= ts ||
(snap_nearest && (ts - cand->stream_time < cand->duration / 2))) { (snap_nearest && (ts - cand->stream_time < cand->duration / 2))) {
@ -1262,12 +1350,37 @@ out:
GST_DEBUG ("Returning segment sn:%" G_GINT64_FORMAT " stream_time:%" GST_DEBUG ("Returning segment sn:%" G_GINT64_FORMAT " stream_time:%"
GST_STIME_FORMAT " duration:%" GST_TIME_FORMAT, res->sequence, GST_STIME_FORMAT " duration:%" GST_TIME_FORMAT, res->sequence,
GST_STIME_ARGS (res->stream_time), GST_TIME_ARGS (res->duration)); GST_STIME_ARGS (res->stream_time), GST_TIME_ARGS (res->duration));
gst_m3u8_media_segment_ref (res);
} else { seek_result->stream_time = res->stream_time;
GST_DEBUG ("Couldn't find a match"); seek_result->segment = gst_m3u8_media_segment_ref (res);
seek_result->found_partial_segment = res->partial_only;
seek_result->part_idx = 0;
return TRUE;
} }
return res; GST_DEBUG ("Couldn't find a match");
return FALSE;
partial_seg_out:
if (res && res->partial_segments != NULL
&& res_part_idx < res->partial_segments->len) {
GstM3U8PartialSegment *part =
g_ptr_array_index (res->partial_segments, res_part_idx);
GST_DEBUG ("Returning partial segment sn:%" G_GINT64_FORMAT
" part_idx %u stream_time:%" GST_STIME_FORMAT " duration:%"
GST_TIME_FORMAT, res->sequence, res_part_idx,
GST_STIME_ARGS (part->stream_time), GST_TIME_ARGS (part->duration));
seek_result->stream_time = part->stream_time;
seek_result->segment = gst_m3u8_media_segment_ref (res);
seek_result->found_partial_segment = TRUE;
seek_result->part_idx = res_part_idx;
return TRUE;
}
GST_DEBUG ("Couldn't find a match");
return FALSE;
} }
static gboolean static gboolean
@ -1780,10 +1893,11 @@ gst_hls_media_playlist_sync_to_segment (GstHLSMediaPlaylist * playlist,
return res; return res;
} }
GstM3U8MediaSegment * gboolean
gst_hls_media_playlist_get_starting_segment (GstHLSMediaPlaylist * self) gst_hls_media_playlist_get_starting_segment (GstHLSMediaPlaylist * self,
gboolean low_latency, GstM3U8SeekResult * seek_result)
{ {
GstM3U8MediaSegment *res; GstM3U8MediaSegment *res = NULL;
GST_DEBUG ("playlist %s", self->uri); GST_DEBUG ("playlist %s", self->uri);
@ -1791,20 +1905,79 @@ gst_hls_media_playlist_get_starting_segment (GstHLSMediaPlaylist * self)
/* For non-live, we just grab the first one */ /* For non-live, we just grab the first one */
res = g_ptr_array_index (self->segments, 0); res = g_ptr_array_index (self->segments, 0);
} else { } else {
/* Live playlist */ GstClockTime hold_back = GST_CLOCK_TIME_NONE;
res = /* Live playlist. If low-latency, use the PART-HOLD-BACK specified distance
g_ptr_array_index (self->segments, * from the end, otherwise HOLD-BACK distance or if that's not provided,
MAX ((gint) self->segments->len - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE - * then 3 target durations */
1, 0)); if (low_latency) {
if (GST_CLOCK_TIME_IS_VALID (self->part_hold_back))
hold_back = self->part_hold_back;
else if (GST_CLOCK_TIME_IS_VALID (self->partial_targetduration))
hold_back = 3 * self->partial_targetduration;
} else {
if (GST_CLOCK_TIME_IS_VALID (self->hold_back))
hold_back = self->hold_back;
else if (GST_CLOCK_TIME_IS_VALID (self->targetduration))
hold_back = 3 * self->targetduration;
}
if (GST_CLOCK_TIME_IS_VALID (hold_back)) {
GstSeekFlags flags = GST_SEEK_FLAG_SNAP_BEFORE | GST_SEEK_FLAG_KEY_UNIT;
GstM3U8MediaSegment *last_seg =
g_ptr_array_index (self->segments, self->segments->len - 1);
GstClockTime playlist_duration =
last_seg->stream_time + last_seg->duration;
GstClockTime target_ts;
/* Clamp the hold back so we don't go below zero */
if (hold_back > playlist_duration)
hold_back = playlist_duration;
target_ts = playlist_duration - hold_back;
GST_DEBUG ("Hold back is %" GST_TIME_FORMAT
" Looking for a segment before %" GST_TIME_FORMAT,
GST_TIME_ARGS (hold_back), GST_TIME_ARGS (target_ts));
if (low_latency)
flags |= GST_HLS_M3U8_SEEK_FLAG_ALLOW_PARTIAL;
if (gst_hls_media_playlist_seek (self, TRUE, flags, target_ts,
seek_result)) {
#ifndef GST_DISABLE_GST_DEBUG
GstClockTime distance_from_edge =
playlist_duration - seek_result->stream_time;
GST_DEBUG ("Found starting position %" GST_TIME_FORMAT " which is %"
GST_TIME_FORMAT " from the live edge",
GST_TIME_ARGS (seek_result->stream_time),
GST_TIME_ARGS (distance_from_edge));
#endif
return TRUE;
}
}
/* Worst case fallback, start 3 fragments from the end */
if (res == NULL) {
res =
g_ptr_array_index (self->segments,
MAX ((gint) self->segments->len -
GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE - 1, 0));
}
} }
if (res) { if (res) {
GST_DEBUG ("Using segment sn:%" G_GINT64_FORMAT " dsn:%" G_GINT64_FORMAT, GST_DEBUG ("Using segment sn:%" G_GINT64_FORMAT " dsn:%" G_GINT64_FORMAT,
res->sequence, res->discont_sequence); res->sequence, res->discont_sequence);
gst_m3u8_media_segment_ref (res);
seek_result->stream_time = res->stream_time;
seek_result->segment = gst_m3u8_media_segment_ref (res);
seek_result->found_partial_segment = FALSE;
seek_result->part_idx = 0;
return TRUE;
} }
return res; return FALSE;
} }
/* Calls this to carry over stream time, DSN, ... from one playlist to another. /* Calls this to carry over stream time, DSN, ... from one playlist to another.

View File

@ -290,8 +290,9 @@ gst_hls_media_playlist_advance_fragment (GstHLSMediaPlaylist * m3u8,
gboolean forward, gboolean forward,
gboolean allow_partial_only_segment); gboolean allow_partial_only_segment);
GstM3U8MediaSegment * gboolean
gst_hls_media_playlist_get_starting_segment (GstHLSMediaPlaylist *self); gst_hls_media_playlist_get_starting_segment (GstHLSMediaPlaylist *self, gboolean low_latency,
GstM3U8SeekResult *seek_result);
GstClockTime GstClockTime
gst_hls_media_playlist_get_duration (GstHLSMediaPlaylist * m3u8); gst_hls_media_playlist_get_duration (GstHLSMediaPlaylist * m3u8);
@ -311,11 +312,12 @@ gboolean
gst_hls_media_playlist_has_lost_sync (GstHLSMediaPlaylist * m3u8, gst_hls_media_playlist_has_lost_sync (GstHLSMediaPlaylist * m3u8,
GstClockTime position); GstClockTime position);
GstM3U8MediaSegment * gboolean
gst_hls_media_playlist_seek (GstHLSMediaPlaylist *playlist, gst_hls_media_playlist_seek (GstHLSMediaPlaylist *playlist,
gboolean forward, gboolean forward,
GstSeekFlags flags, GstSeekFlags flags,
GstClockTimeDiff ts); GstClockTimeDiff ts,
GstM3U8SeekResult *seek_result);
gboolean gboolean
gst_hls_media_playlist_find_position (GstHLSMediaPlaylist *playlist, gst_hls_media_playlist_find_position (GstHLSMediaPlaylist *playlist,

View File

@ -675,11 +675,15 @@ GST_START_TEST (test_advance_fragment)
{ {
GstHLSMediaPlaylist *pl; GstHLSMediaPlaylist *pl;
GstM3U8MediaSegment *mf; GstM3U8MediaSegment *mf;
GstM3U8SeekResult seek_result;
pl = load_m3u8 (BYTE_RANGES_PLAYLIST); pl = load_m3u8 (BYTE_RANGES_PLAYLIST);
/* Check the next fragment */ /* Check the next fragment */
mf = gst_hls_media_playlist_get_starting_segment (pl); fail_unless (gst_hls_media_playlist_get_starting_segment (pl, FALSE,
&seek_result) == TRUE);
mf = seek_result.segment;
fail_unless (mf != NULL); fail_unless (mf != NULL);
assert_equals_int (mf->discont, FALSE); assert_equals_int (mf->discont, FALSE);
assert_equals_string (mf->uri, "http://media.example.com/all.ts"); assert_equals_string (mf->uri, "http://media.example.com/all.ts");