gstreamer/subprojects/gst-plugins-good/ext/adaptivedemux2/hls/gsthlsdemux-stream.c
Edward Hervey e7ab454cf5 hlsdemux2: Fix failure to find a replacement segment on resync
If we end up with a segment with an internal time that varies from the supposed
one, this could be for two reasons:
* We guess-timated the wrong segment to go to when advancing or switching
  variants. In that case we try to find the actual segment to go to (just before
  this change).
* There was a complete playlist change (for whatever reason) and we can't find a
  replacement. In that case we want to carry on playback from this position but
  need to remember that we moved (by setting the stream to DISCONT, and
  resetting the new mapping).

Fixes playback on several broken stream

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6961>
2024-05-30 07:59:25 +00:00

2164 lines
76 KiB
C

/* GStreamer
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
* Copyright (C) 2010 Andoni Morales Alastruey <ylatuya@gmail.com>
* Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
* Author: Youness Alaoui <youness.alaoui@collabora.co.uk>, Collabora Ltd.
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
* Copyright (C) 2014 Sebastian Dröge <sebastian@centricular.com>
* Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
*
* Copyright (C) 2021-2022 Centricular Ltd
* Author: Edward Hervey <edward@centricular.com>
* Author: Jan Schmidt <jan@centricular.com>
*
* gsthlsdemux-stream.c:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <string.h>
#include <gst/base/gsttypefindhelper.h>
#include <gst/tag/tag.h>
#include <glib/gi18n-lib.h>
#include "gsthlsdemux.h"
#include "gsthlsdemux-stream.h"
GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux2_debug);
#define GST_CAT_DEFAULT gst_hls_demux2_debug
/* Maximum values for mpeg-ts DTS values */
#define MPEG_TS_MAX_PTS (((((guint64)1) << 33) * (guint64)100000) / 9)
static GstBuffer *gst_hls_demux_decrypt_fragment (GstHLSDemux * demux,
GstHLSDemuxStream * stream, GstBuffer * encrypted_buffer, GError ** err);
static gboolean
gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream,
const guint8 * key_data, const guint8 * iv_data);
static void gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream);
static gboolean
gst_hls_demux_stream_start_fragment (GstAdaptiveDemux2Stream * stream);
static GstFlowReturn
gst_hls_demux_stream_finish_fragment (GstAdaptiveDemux2Stream * stream);
static GstFlowReturn gst_hls_demux_stream_data_received (GstAdaptiveDemux2Stream
* stream, GstBuffer * buffer);
static gboolean gst_hls_demux_stream_has_next_fragment (GstAdaptiveDemux2Stream
* stream);
static GstFlowReturn
gst_hls_demux_stream_advance_fragment (GstAdaptiveDemux2Stream * stream);
static GstFlowReturn
gst_hls_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream);
static GstFlowReturn
gst_hls_demux_stream_submit_request (GstAdaptiveDemux2Stream * stream,
DownloadRequest * download_req);
static void gst_hls_demux_stream_start (GstAdaptiveDemux2Stream * stream);
static void gst_hls_demux_stream_stop (GstAdaptiveDemux2Stream * stream);
static void gst_hls_demux_stream_create_tracks (GstAdaptiveDemux2Stream *
stream);
static gboolean gst_hls_demux_stream_select_bitrate (GstAdaptiveDemux2Stream *
stream, guint64 bitrate);
static GstClockTime
gst_hls_demux_stream_get_presentation_offset (GstAdaptiveDemux2Stream * stream);
static void gst_hls_demux_stream_finalize (GObject * object);
#define gst_hls_demux_stream_parent_class stream_parent_class
G_DEFINE_TYPE (GstHLSDemuxStream, gst_hls_demux_stream,
GST_TYPE_ADAPTIVE_DEMUX2_STREAM);
static void
gst_hls_demux_stream_class_init (GstHLSDemuxStreamClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstAdaptiveDemux2StreamClass *adaptivedemux2stream_class =
GST_ADAPTIVE_DEMUX2_STREAM_CLASS (klass);
gobject_class->finalize = gst_hls_demux_stream_finalize;
adaptivedemux2stream_class->update_fragment_info =
gst_hls_demux_stream_update_fragment_info;
adaptivedemux2stream_class->submit_request =
gst_hls_demux_stream_submit_request;
adaptivedemux2stream_class->has_next_fragment =
gst_hls_demux_stream_has_next_fragment;
adaptivedemux2stream_class->stream_seek = gst_hls_demux_stream_seek;
adaptivedemux2stream_class->advance_fragment =
gst_hls_demux_stream_advance_fragment;
adaptivedemux2stream_class->select_bitrate =
gst_hls_demux_stream_select_bitrate;
adaptivedemux2stream_class->start = gst_hls_demux_stream_start;
adaptivedemux2stream_class->stop = gst_hls_demux_stream_stop;
adaptivedemux2stream_class->create_tracks =
gst_hls_demux_stream_create_tracks;
adaptivedemux2stream_class->start_fragment =
gst_hls_demux_stream_start_fragment;
adaptivedemux2stream_class->finish_fragment =
gst_hls_demux_stream_finish_fragment;
adaptivedemux2stream_class->data_received =
gst_hls_demux_stream_data_received;
adaptivedemux2stream_class->get_presentation_offset =
gst_hls_demux_stream_get_presentation_offset;
}
static void
gst_hls_demux_stream_init (GstHLSDemuxStream * stream)
{
stream->parser_type = GST_HLS_PARSER_NONE;
stream->do_typefind = TRUE;
stream->reset_pts = TRUE;
stream->presentation_offset = 60 * GST_SECOND;
stream->pdt_tag_sent = FALSE;
}
void
gst_hls_demux_stream_clear_pending_data (GstHLSDemuxStream * hls_stream,
gboolean force)
{
GST_DEBUG_OBJECT (hls_stream, "force : %d", force);
if (hls_stream->pending_encrypted_data)
gst_adapter_clear (hls_stream->pending_encrypted_data);
gst_buffer_replace (&hls_stream->pending_decrypted_buffer, NULL);
gst_buffer_replace (&hls_stream->pending_typefind_buffer, NULL);
if (force || !hls_stream->pending_data_is_header) {
gst_buffer_replace (&hls_stream->pending_segment_data, NULL);
hls_stream->pending_data_is_header = FALSE;
}
hls_stream->current_offset = -1;
hls_stream->process_buffer_content = TRUE;
gst_hls_demux_stream_decrypt_end (hls_stream);
}
GstFlowReturn
gst_hls_demux_stream_seek (GstAdaptiveDemux2Stream * stream, gboolean forward,
GstSeekFlags flags, GstClockTimeDiff ts, GstClockTimeDiff * final_ts)
{
GstFlowReturn ret = GST_FLOW_OK;
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GST_DEBUG_OBJECT (stream,
"is_variant:%d media:%p current_variant:%p forward:%d ts:%"
GST_TIME_FORMAT, hls_stream->is_variant, hls_stream->current_rendition,
hlsdemux->current_variant, forward, GST_TIME_ARGS (ts));
/* If this stream doesn't have a playlist yet, we can't seek on it */
if (!hls_stream->playlist_fetched) {
return GST_ADAPTIVE_DEMUX_FLOW_BUSY;
}
/* Allow jumping to partial segments in the last 2 segments in LL-HLS */
if (GST_HLS_MEDIA_PLAYLIST_IS_LIVE (hls_stream->playlist))
flags |= GST_HLS_M3U8_SEEK_FLAG_ALLOW_PARTIAL;
GstM3U8SeekResult seek_result;
if (gst_hls_media_playlist_seek (hls_stream->playlist, forward, flags, ts,
&seek_result)) {
if (hls_stream->current_segment)
gst_m3u8_media_segment_unref (hls_stream->current_segment);
hls_stream->current_segment = seek_result.segment;
hls_stream->in_partial_segments = seek_result.found_partial_segment;
hls_stream->part_idx = seek_result.part_idx;
hls_stream->reset_pts = TRUE;
if (final_ts)
*final_ts = seek_result.stream_time;
} else {
GST_WARNING_OBJECT (stream, "Seeking failed");
ret = GST_FLOW_ERROR;
}
return ret;
}
static GstCaps *
get_caps_of_stream_type (GstCaps * full_caps, GstStreamType streamtype)
{
GstCaps *ret = NULL;
guint i;
for (i = 0; i < gst_caps_get_size (full_caps); i++) {
GstStructure *st = gst_caps_get_structure (full_caps, i);
if (gst_hls_get_stream_type_from_structure (st) == streamtype) {
ret = gst_caps_new_empty ();
gst_caps_append_structure (ret, gst_structure_copy (st));
break;
}
}
return ret;
}
static GstHLSRenditionStream *
find_uriless_rendition (GstHLSDemux * demux, GstStreamType stream_type)
{
GList *tmp;
for (tmp = demux->master->renditions; tmp; tmp = tmp->next) {
GstHLSRenditionStream *media = tmp->data;
if (media->uri == NULL &&
gst_stream_type_from_hls_type (media->mtype) == stream_type)
return media;
}
return NULL;
}
static void
gst_hls_demux_stream_create_tracks (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GstHLSDemuxStream *hlsdemux_stream = (GstHLSDemuxStream *) stream;
guint i;
GstStreamType uriless_types = 0;
GstCaps *variant_caps = NULL;
GST_DEBUG_OBJECT (stream, "Update tracks of variant stream");
if (hlsdemux->master->have_codecs) {
variant_caps = gst_hls_master_playlist_get_common_caps (hlsdemux->master);
}
/* Use the stream->stream_collection and manifest to create the appropriate tracks */
for (i = 0; i < gst_stream_collection_get_size (stream->stream_collection);
i++) {
GstStream *gst_stream =
gst_stream_collection_get_stream (stream->stream_collection, i);
GstStreamType stream_type = gst_stream_get_stream_type (gst_stream);
GstAdaptiveDemuxTrack *track;
GstHLSRenditionStream *embedded_media = NULL;
/* tracks from the variant streams should be prefered over those provided by renditions */
GstStreamFlags flags =
gst_stream_get_stream_flags (gst_stream) | GST_STREAM_FLAG_SELECT;
GstCaps *manifest_caps = NULL;
if (stream_type == GST_STREAM_TYPE_UNKNOWN)
continue;
if (variant_caps)
manifest_caps = get_caps_of_stream_type (variant_caps, stream_type);
hlsdemux_stream->rendition_type |= stream_type;
if ((uriless_types & stream_type) == 0) {
/* Do we have a uriless media for this stream type */
/* Find if there is a rendition without URI, it will be provided by this variant */
embedded_media = find_uriless_rendition (hlsdemux, stream_type);
/* Remember we used this type for a embedded media */
uriless_types |= stream_type;
}
if (embedded_media) {
GstTagList *tags = gst_stream_get_tags (gst_stream);
GST_DEBUG_OBJECT (stream, "Adding track '%s' to main variant stream",
embedded_media->name);
track =
gst_hls_demux_new_track_for_rendition (hlsdemux, embedded_media,
manifest_caps, flags,
tags ? gst_tag_list_make_writable (tags) : tags);
} else {
gchar *stream_id;
stream_id =
g_strdup_printf ("main-%s-%d", gst_stream_type_get_name (stream_type),
i);
GST_DEBUG_OBJECT (stream, "Adding track '%s' to main variant stream",
stream_id);
track =
gst_adaptive_demux_track_new (stream->demux, stream_type,
flags, stream_id, manifest_caps, NULL);
g_free (stream_id);
}
track->upstream_stream_id =
g_strdup (gst_stream_get_stream_id (gst_stream));
gst_adaptive_demux2_stream_add_track (stream, track);
gst_adaptive_demux_track_unref (track);
}
if (variant_caps)
gst_caps_unref (variant_caps);
/* Update the stream object with rendition types.
* FIXME: rendition_type could be removed */
stream->stream_type = hlsdemux_stream->rendition_type;
}
static gboolean
gst_hls_demux_stream_start_fragment (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
const GstHLSKey *key;
GstHLSMediaPlaylist *m3u8;
GST_DEBUG_OBJECT (stream, "Fragment starting");
gst_hls_demux_stream_clear_pending_data (hls_stream, FALSE);
/* If no decryption is needed, there's nothing to be done here */
if (hls_stream->current_key == NULL)
return TRUE;
m3u8 = hls_stream->playlist;
key = gst_hls_demux_get_key (hlsdemux, hls_stream->current_key,
m3u8->uri, m3u8->allowcache);
if (key == NULL)
goto key_failed;
if (!gst_hls_demux_stream_decrypt_start (hls_stream, key->data,
hls_stream->current_iv))
goto decrypt_start_failed;
return TRUE;
key_failed:
{
GST_ELEMENT_ERROR (hlsdemux, STREAM, DECRYPT_NOKEY,
("Couldn't retrieve key for decryption"), (NULL));
GST_WARNING_OBJECT (hlsdemux, "Failed to decrypt data");
return FALSE;
}
decrypt_start_failed:
{
GST_ELEMENT_ERROR (hlsdemux, STREAM, DECRYPT, ("Failed to start decrypt"),
("Couldn't set key and IV or plugin was built without crypto library"));
return FALSE;
}
}
static GstHLSParserType
caps_to_parser_type (const GstCaps * caps)
{
const GstStructure *s = gst_caps_get_structure (caps, 0);
if (gst_structure_has_name (s, "video/mpegts"))
return GST_HLS_PARSER_MPEGTS;
if (gst_structure_has_name (s, "application/x-id3"))
return GST_HLS_PARSER_ID3;
if (gst_structure_has_name (s, "application/x-subtitle-vtt"))
return GST_HLS_PARSER_WEBVTT;
if (gst_structure_has_name (s, "video/quicktime"))
return GST_HLS_PARSER_ISOBMFF;
return GST_HLS_PARSER_NONE;
}
/* Identify the nature of data for this stream
*
* Will also setup the appropriate parser (tsreader) if needed
*
* Consumes the input buffer when it returns FALSE, but
* replaces / returns the input buffer in the `buffer` parameter
* when it returns TRUE.
*
* Returns TRUE if we are done with typefinding */
static gboolean
gst_hls_demux_typefind_stream (GstHLSDemux * hlsdemux,
GstAdaptiveDemux2Stream * stream, GstBuffer ** out_buffer, gboolean at_eos,
GstFlowReturn * ret)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); // FIXME: pass HlsStream into function
GstCaps *caps = NULL;
guint buffer_size;
GstTypeFindProbability prob = GST_TYPE_FIND_NONE;
GstMapInfo info;
GstBuffer *buffer = *out_buffer;
if (hls_stream->pending_typefind_buffer) {
/* Append to the existing typefind buffer and create a new one that
* we'll return (or consume below) */
buffer = *out_buffer =
gst_buffer_append (hls_stream->pending_typefind_buffer, buffer);
hls_stream->pending_typefind_buffer = NULL;
}
gst_buffer_map (buffer, &info, GST_MAP_READ);
buffer_size = info.size;
/* Typefind could miss if buffer is too small. In this case we
* will retry later */
if (buffer_size >= (2 * 1024) || at_eos) {
caps =
gst_type_find_helper_for_data (GST_OBJECT_CAST (hlsdemux), info.data,
info.size, &prob);
}
if (G_UNLIKELY (!caps)) {
/* Won't need this mapping any more all paths return inside this if() */
gst_buffer_unmap (buffer, &info);
/* Only fail typefinding if we already a good amount of data
* and we still don't know the type */
if (buffer_size > (2 * 1024 * 1024) || at_eos) {
GST_ELEMENT_ERROR (hlsdemux, STREAM, TYPE_NOT_FOUND,
("Could not determine type of stream"), (NULL));
gst_buffer_unref (buffer);
*ret = GST_FLOW_NOT_NEGOTIATED;
} else {
GST_LOG_OBJECT (stream, "Not enough data to typefind");
hls_stream->pending_typefind_buffer = buffer; /* Transfer the ref */
*ret = GST_FLOW_OK;
}
*out_buffer = NULL;
return FALSE;
}
GST_DEBUG_OBJECT (stream,
"Typefind result: %" GST_PTR_FORMAT " prob:%d", caps, prob);
if (hls_stream->parser_type == GST_HLS_PARSER_NONE) {
hls_stream->parser_type = caps_to_parser_type (caps);
if (hls_stream->parser_type == GST_HLS_PARSER_NONE) {
GST_WARNING_OBJECT (stream,
"Unsupported stream type %" GST_PTR_FORMAT, caps);
GST_MEMDUMP_OBJECT (stream, "unknown data", info.data,
MIN (info.size, 128));
gst_buffer_unref (buffer);
*ret = GST_FLOW_ERROR;
return FALSE;
}
if (hls_stream->parser_type == GST_HLS_PARSER_ISOBMFF)
hls_stream->presentation_offset = 0;
}
gst_adaptive_demux2_stream_set_caps (stream, caps);
hls_stream->do_typefind = FALSE;
gst_buffer_unmap (buffer, &info);
/* We are done with typefinding. Doesn't consume the input buffer */
*ret = GST_FLOW_OK;
return TRUE;
}
/* Compute the stream time for the given internal time, based on the provided
* time map.
*
* Will handle mpeg-ts wraparound. */
GstClockTimeDiff
gst_hls_internal_to_stream_time (GstHLSTimeMap * map,
GstClockTime internal_time)
{
if (map->internal_time == GST_CLOCK_TIME_NONE)
return GST_CLOCK_STIME_NONE;
/* Handle MPEG-TS Wraparound */
if (internal_time < map->internal_time &&
map->internal_time - internal_time > (MPEG_TS_MAX_PTS / 2))
internal_time += MPEG_TS_MAX_PTS;
return (map->stream_time + internal_time - map->internal_time);
}
/* Handle the internal time discovered on a segment.
*
* This function is called by the individual buffer parsers once they have
* extracted that internal time (which is most of the time based on mpegts time,
* but can also be ISOBMFF pts).
*
* This will update the time map when appropriate.
*
* If a synchronization issue is detected, the appropriate steps will be taken
* and the RESYNC return value will be returned
*/
GstHLSParserResult
gst_hlsdemux_stream_handle_internal_time (GstHLSDemuxStream * hls_stream,
GstClockTime internal_time)
{
GstM3U8MediaSegment *current_segment = hls_stream->current_segment;
GstHLSTimeMap *map;
GstClockTimeDiff current_stream_time;
GstClockTimeDiff real_stream_time, difference;
g_return_val_if_fail (current_segment != NULL, GST_HLS_PARSER_RESULT_ERROR);
current_stream_time = current_segment->stream_time;
if (hls_stream->in_partial_segments) {
/* If the current partial segment is valid, update the stream current position to the partial
* segment stream_time, otherwise leave it alone and fix it up later when we resync */
if (current_segment->partial_segments
&& hls_stream->part_idx < current_segment->partial_segments->len) {
GstM3U8PartialSegment *part =
g_ptr_array_index (current_segment->partial_segments,
hls_stream->part_idx);
current_stream_time = part->stream_time;
}
}
GST_DEBUG_OBJECT (hls_stream,
"Got internal time %" GST_TIME_FORMAT " for current segment stream time %"
GST_STIME_FORMAT, GST_TIME_ARGS (internal_time),
GST_STIME_ARGS (current_stream_time));
GstHLSDemux *demux =
GST_HLS_DEMUX_CAST (GST_ADAPTIVE_DEMUX2_STREAM_CAST (hls_stream)->demux);
map = gst_hls_demux_find_time_map (demux, current_segment->discont_sequence);
/* Time mappings will always be created upon initial parsing and when advancing */
g_assert (map);
/* Handle the first internal time of a discont sequence. We can only store/use
* those values for variant streams. */
if (!GST_CLOCK_TIME_IS_VALID (map->internal_time)) {
if (!hls_stream->is_variant) {
GST_WARNING_OBJECT (hls_stream,
"Got data from a new discont sequence on a rendition stream, can't validate stream time");
return GST_HLS_PARSER_RESULT_DONE;
}
GST_DEBUG_OBJECT (hls_stream,
"Updating time map dsn:%" G_GINT64_FORMAT " stream_time:%"
GST_STIME_FORMAT " internal_time:%" GST_TIME_FORMAT, map->dsn,
GST_STIME_ARGS (current_stream_time), GST_TIME_ARGS (internal_time));
/* The stream time for a mapping should always be positive ! */
g_assert (current_stream_time >= 0);
if (hls_stream->parser_type == GST_HLS_PARSER_ISOBMFF)
hls_stream->presentation_offset = internal_time - current_stream_time;
gst_time_map_set_values (map, current_stream_time, internal_time,
current_segment->datetime);
gst_hls_demux_start_rendition_streams (demux);
return GST_HLS_PARSER_RESULT_DONE;
}
/* The information in a discont is always valid */
if (current_segment->discont) {
GST_DEBUG_OBJECT (hls_stream,
"DISCONT segment, Updating time map to stream_time:%" GST_STIME_FORMAT
" internal_time:%" GST_TIME_FORMAT, GST_STIME_ARGS (internal_time),
GST_TIME_ARGS (current_stream_time));
gst_time_map_set_values (map, current_stream_time, internal_time,
current_segment->datetime);
return GST_HLS_PARSER_RESULT_DONE;
}
/* Check if the segment is the expected one */
real_stream_time = gst_hls_internal_to_stream_time (map, internal_time);
difference = current_stream_time - real_stream_time;
GST_DEBUG_OBJECT (hls_stream,
"Segment contains stream time %" GST_STIME_FORMAT
" difference against expected : %" GST_STIME_FORMAT,
GST_STIME_ARGS (real_stream_time), GST_STIME_ARGS (difference));
/* We allow a tolerance of 3-4 frames between the estimated and observed
* stream time. */
if (ABS (difference) > 100 * GST_MSECOND) {
GstClockTimeDiff wrong_position_threshold =
hls_stream->current_segment->duration / 2;
/* Update the value */
GST_DEBUG_OBJECT (hls_stream,
"Updating current stream time to %" GST_STIME_FORMAT,
GST_STIME_ARGS (real_stream_time));
/* For LL-HLS, make sure to update and recalculate stream time from
* the right partial segment if playing one */
if (hls_stream->in_partial_segments && hls_stream->part_idx != 0) {
if (current_segment->partial_segments
&& hls_stream->part_idx < current_segment->partial_segments->len) {
GstM3U8PartialSegment *part =
g_ptr_array_index (current_segment->partial_segments,
hls_stream->part_idx);
part->stream_time = real_stream_time;
gst_hls_media_playlist_recalculate_stream_time_from_part
(hls_stream->playlist, hls_stream->current_segment,
hls_stream->part_idx);
/* When playing partial segments, the "Wrong position" threshold should be
* half the part duration */
wrong_position_threshold = part->duration / 2;
}
} else {
/* Aligned to the start of the segment, update there */
current_segment->stream_time = real_stream_time;
gst_hls_media_playlist_recalculate_stream_time (hls_stream->playlist,
hls_stream->current_segment);
}
gst_hls_media_playlist_dump (hls_stream->playlist);
if (ABS (difference) > wrong_position_threshold) {
GstAdaptiveDemux2Stream *stream = (GstAdaptiveDemux2Stream *) hls_stream;
GstM3U8SeekResult seek_result;
/* We are at the wrong segment, try to figure out the *actual* segment */
GST_DEBUG_OBJECT (hls_stream,
"Trying to find the correct segment in the playlist for %"
GST_STIME_FORMAT, GST_STIME_ARGS (current_stream_time));
if (gst_hls_media_playlist_find_position (hls_stream->playlist,
current_stream_time, hls_stream->in_partial_segments,
&seek_result)) {
GST_DEBUG_OBJECT (hls_stream, "Synced to position %" GST_STIME_FORMAT,
GST_STIME_ARGS (seek_result.stream_time));
gst_m3u8_media_segment_unref (hls_stream->current_segment);
hls_stream->current_segment = seek_result.segment;
hls_stream->in_partial_segments = seek_result.found_partial_segment;
hls_stream->part_idx = seek_result.part_idx;
/* Ask parent class to restart this fragment */
return GST_HLS_PARSER_RESULT_RESYNC;
}
GST_WARNING_OBJECT (hls_stream,
"Could not find a replacement stream, carrying on with segment");
stream->discont = TRUE;
stream->fragment.stream_time = current_stream_time;
gst_time_map_set_values (map, current_stream_time, internal_time,
hls_stream->current_segment->datetime);
}
}
return GST_HLS_PARSER_RESULT_DONE;
}
static GstHLSParserResult
gst_hls_demux_handle_buffer_content (GstHLSDemux * demux,
GstHLSDemuxStream * hls_stream, gboolean draining, GstBuffer ** buffer)
{
GstHLSTimeMap *map;
GstAdaptiveDemux2Stream *stream = (GstAdaptiveDemux2Stream *) hls_stream;
GstClockTimeDiff current_stream_time =
hls_stream->current_segment->stream_time;
GstClockTime current_duration = hls_stream->current_segment->duration;
GstHLSParserResult parser_ret;
GST_LOG_OBJECT (stream,
"stream_time:%" GST_STIME_FORMAT " duration:%" GST_TIME_FORMAT
" discont:%d draining:%d header:%d index:%d",
GST_STIME_ARGS (current_stream_time), GST_TIME_ARGS (current_duration),
hls_stream->current_segment->discont, draining,
stream->downloading_header, stream->downloading_index);
/* FIXME : Replace the boolean parser return value (and this function's return
* value) by an enum which clearly specifies whether:
*
* * The content parsing happened succesfully and it no longer needs to be
* called for the remainder of this fragment
* * More data is needed in order to parse the data
* * There was a fatal error parsing the contents (ex: invalid/incompatible
* content)
* * The computed fragment stream time is out of sync
*/
g_assert (demux->mappings);
map =
gst_hls_demux_find_time_map (demux,
hls_stream->current_segment->discont_sequence);
if (!map) {
/* For rendition streams, we can't do anything without time mapping */
if (!hls_stream->is_variant) {
GST_DEBUG_OBJECT (stream,
"No available time mapping for dsn:%" G_GINT64_FORMAT
" using estimated stream time",
hls_stream->current_segment->discont_sequence);
goto out_done;
}
/* Variants will be able to fill in the the time mapping, so we can carry on without a time mapping */
} else {
GST_DEBUG_OBJECT (stream,
"Using mapping dsn:%" G_GINT64_FORMAT " stream_time:%" GST_TIME_FORMAT
" internal_time:%" GST_TIME_FORMAT, map->dsn,
GST_TIME_ARGS (map->stream_time), GST_TIME_ARGS (map->internal_time));
}
switch (hls_stream->parser_type) {
case GST_HLS_PARSER_MPEGTS:
parser_ret =
gst_hlsdemux_handle_content_mpegts (demux, hls_stream, draining,
buffer);
break;
case GST_HLS_PARSER_ID3:
parser_ret =
gst_hlsdemux_handle_content_id3 (demux, hls_stream, draining, buffer);
break;
case GST_HLS_PARSER_WEBVTT:
{
/* Furthermore it will handle timeshifting itself */
parser_ret =
gst_hlsdemux_handle_content_webvtt (demux, hls_stream, draining,
buffer);
break;
}
case GST_HLS_PARSER_ISOBMFF:
parser_ret =
gst_hlsdemux_handle_content_isobmff (demux, hls_stream, draining,
buffer);
break;
case GST_HLS_PARSER_NONE:
default:
{
GST_ERROR_OBJECT (stream, "Unknown stream type");
goto out_error;
}
}
if (parser_ret == GST_HLS_PARSER_RESULT_NEED_MORE_DATA) {
if (stream->downloading_index || stream->downloading_header)
goto out_need_more;
/* Else if we're draining, it's an error */
if (draining)
goto out_error;
/* Else we just need more data */
goto out_need_more;
}
if (parser_ret == GST_HLS_PARSER_RESULT_ERROR)
goto out_error;
if (parser_ret == GST_HLS_PARSER_RESULT_RESYNC)
goto out_resync;
out_done:
GST_DEBUG_OBJECT (stream, "Done. Finished parsing");
return GST_HLS_PARSER_RESULT_DONE;
out_error:
GST_DEBUG_OBJECT (stream, "Done. Error while parsing");
return GST_HLS_PARSER_RESULT_ERROR;
out_need_more:
GST_DEBUG_OBJECT (stream, "Done. Need more data");
return GST_HLS_PARSER_RESULT_NEED_MORE_DATA;
out_resync:
GST_DEBUG_OBJECT (stream, "Done. Resync required");
return GST_HLS_PARSER_RESULT_RESYNC;
}
static GstFlowReturn
gst_hls_demux_stream_handle_buffer (GstAdaptiveDemux2Stream * stream,
GstBuffer * buffer, gboolean at_eos)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); // FIXME: pass HlsStream into function
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
GstFlowReturn ret = GST_FLOW_OK;
GstBuffer *pending_header_data = NULL;
/* If current segment is not present, this means that a playlist update
* happened between the moment ::update_fragment_info() was called and the
* moment we received data. And that playlist update couldn't match the
* current position. This will happen in live playback when we are downloading
* too slowly, therefore we try to "catch up" back to live
*/
if (hls_stream->current_segment == NULL) {
GST_WARNING_OBJECT (stream, "Lost sync");
/* Drop the buffer */
gst_buffer_unref (buffer);
return GST_ADAPTIVE_DEMUX_FLOW_LOST_SYNC;
}
GST_DEBUG_OBJECT (stream,
"buffer:%p at_eos:%d do_typefind:%d uri:%s", buffer, at_eos,
hls_stream->do_typefind, GST_STR_NULL (stream->fragment.uri));
if (buffer == NULL)
goto out;
/* If we need to do typefind and we're not done with it (or we errored), return */
if (G_UNLIKELY (hls_stream->do_typefind) &&
!gst_hls_demux_typefind_stream (hlsdemux, stream, &buffer, at_eos,
&ret)) {
goto out;
}
g_assert (hls_stream->pending_typefind_buffer == NULL);
if (hls_stream->process_buffer_content) {
GstHLSParserResult parse_ret;
if (hls_stream->pending_segment_data) {
if (hls_stream->pending_data_is_header) {
/* Keep a copy of the header data in case we need to requeue it
* due to GST_ADAPTIVE_DEMUX_FLOW_RESTART_FRAGMENT below */
pending_header_data = gst_buffer_ref (hls_stream->pending_segment_data);
}
buffer = gst_buffer_append (hls_stream->pending_segment_data, buffer);
hls_stream->pending_segment_data = NULL;
}
/* Try to get the timing information */
parse_ret =
gst_hls_demux_handle_buffer_content (hlsdemux, hls_stream, at_eos,
&buffer);
switch (parse_ret) {
case GST_HLS_PARSER_RESULT_NEED_MORE_DATA:
/* If we don't have enough, store and return */
hls_stream->pending_segment_data = buffer;
hls_stream->pending_data_is_header =
(stream->downloading_header == TRUE);
if (hls_stream->pending_data_is_header)
stream->send_segment = TRUE;
goto out;
case GST_HLS_PARSER_RESULT_ERROR:
/* Error, drop buffer and return */
gst_buffer_unref (buffer);
ret = GST_FLOW_ERROR;
goto out;
case GST_HLS_PARSER_RESULT_RESYNC:
/* Resync, drop buffer and return */
gst_buffer_unref (buffer);
ret = GST_ADAPTIVE_DEMUX_FLOW_RESTART_FRAGMENT;
/* If we had a pending set of header data, requeue it */
if (pending_header_data != NULL) {
g_assert (hls_stream->pending_segment_data == NULL);
GST_DEBUG_OBJECT (hls_stream,
"Requeueing header data %" GST_PTR_FORMAT
" before returning RESTART_FRAGMENT", pending_header_data);
hls_stream->pending_segment_data = pending_header_data;
pending_header_data = NULL;
}
goto out;
case GST_HLS_PARSER_RESULT_DONE:
/* Done parsing, carry on */
hls_stream->process_buffer_content = FALSE;
break;
}
}
if (!buffer)
goto out;
buffer = gst_buffer_make_writable (buffer);
GST_BUFFER_OFFSET (buffer) = hls_stream->current_offset;
hls_stream->current_offset += gst_buffer_get_size (buffer);
GST_BUFFER_OFFSET_END (buffer) = hls_stream->current_offset;
GST_DEBUG_OBJECT (stream, "We have a buffer, pushing: %" GST_PTR_FORMAT,
buffer);
ret = gst_adaptive_demux2_stream_push_buffer (stream, buffer);
out:
if (pending_header_data != NULL) {
/* Throw away the pending header data now. If it wasn't consumed above,
* we won't need it */
gst_buffer_unref (pending_header_data);
}
GST_DEBUG_OBJECT (stream, "Returning %s", gst_flow_get_name (ret));
return ret;
}
static GstFlowReturn
gst_hls_demux_stream_finish_fragment (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream); // FIXME: pass HlsStream into function
GstFlowReturn ret = GST_FLOW_OK;
GST_DEBUG_OBJECT (stream, "Finishing %ssegment uri:%s",
hls_stream->in_partial_segments ? "partial " : "",
GST_STR_NULL (stream->fragment.uri));
/* Drain all pending data */
if (hls_stream->current_key)
gst_hls_demux_stream_decrypt_end (hls_stream);
if (hls_stream->current_segment && stream->last_ret == GST_FLOW_OK) {
if (hls_stream->pending_decrypted_buffer) {
if (hls_stream->current_key) {
GstMapInfo info;
gssize unpadded_size;
/* Handle pkcs7 unpadding here */
gst_buffer_map (hls_stream->pending_decrypted_buffer, &info,
GST_MAP_READ);
unpadded_size = info.size - info.data[info.size - 1];
gst_buffer_unmap (hls_stream->pending_decrypted_buffer, &info);
gst_buffer_resize (hls_stream->pending_decrypted_buffer, 0,
unpadded_size);
}
ret =
gst_hls_demux_stream_handle_buffer (stream,
hls_stream->pending_decrypted_buffer, TRUE);
hls_stream->pending_decrypted_buffer = NULL;
}
if (ret == GST_FLOW_OK || ret == GST_FLOW_NOT_LINKED) {
if (G_UNLIKELY (hls_stream->pending_typefind_buffer)) {
GstBuffer *buf = hls_stream->pending_typefind_buffer;
hls_stream->pending_typefind_buffer = NULL;
gst_hls_demux_stream_handle_buffer (stream, buf, TRUE);
}
if (hls_stream->pending_segment_data) {
GstBuffer *buf = hls_stream->pending_segment_data;
hls_stream->pending_segment_data = NULL;
ret = gst_hls_demux_stream_handle_buffer (stream, buf, TRUE);
}
}
}
gst_hls_demux_stream_clear_pending_data (hls_stream, FALSE);
if (G_UNLIKELY (stream->downloading_header || stream->downloading_index))
return GST_FLOW_OK;
if (hls_stream->current_segment == NULL) {
/* We can't advance, we just return OK for now and let the base class
* trigger a new download (or fail and resync itself) */
GST_DEBUG_OBJECT (stream, "Can't advance - current_segment is NULL");
return GST_FLOW_OK;
}
if (ret == GST_FLOW_OK || ret == GST_FLOW_NOT_LINKED) {
GstClockTime duration = hls_stream->current_segment->duration;
/* We can update the stream current position with a more accurate value
* before advancing. Note that we don't have any period so we can set the
* stream_time as-is on the stream current position */
if (hls_stream->in_partial_segments) {
GstM3U8MediaSegment *cur_segment = hls_stream->current_segment;
/* If the current partial segment is valid, update the stream current position, otherwise
* leave it alone and fix it up later when we resync */
if (cur_segment->partial_segments
&& hls_stream->part_idx < cur_segment->partial_segments->len) {
GstM3U8PartialSegment *part =
g_ptr_array_index (cur_segment->partial_segments,
hls_stream->part_idx);
stream->current_position = part->stream_time;
duration = part->duration;
}
} else {
stream->current_position = hls_stream->current_segment->stream_time;
}
return gst_adaptive_demux2_stream_advance_fragment (stream, duration);
}
return ret;
}
static GstFlowReturn
gst_hls_demux_stream_data_received (GstAdaptiveDemux2Stream * stream,
GstBuffer * buffer)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
GstM3U8MediaSegment *file = hls_stream->current_segment;
if (hls_stream->current_segment == NULL)
return GST_ADAPTIVE_DEMUX_FLOW_LOST_SYNC;
if (hls_stream->current_offset == -1)
hls_stream->current_offset = 0;
/* Is it encrypted? */
if (hls_stream->current_key) {
GError *err = NULL;
gsize size;
GstBuffer *decrypted_buffer;
GstBuffer *tmp_buffer;
if (hls_stream->pending_encrypted_data == NULL)
hls_stream->pending_encrypted_data = gst_adapter_new ();
gst_adapter_push (hls_stream->pending_encrypted_data, buffer);
size = gst_adapter_available (hls_stream->pending_encrypted_data);
/* must be a multiple of 16 */
size &= (~0xF);
if (size == 0) {
return GST_FLOW_OK;
}
buffer = gst_adapter_take_buffer (hls_stream->pending_encrypted_data, size);
decrypted_buffer =
gst_hls_demux_decrypt_fragment (hlsdemux, hls_stream, buffer, &err);
if (err) {
GST_ELEMENT_ERROR (hlsdemux, STREAM, DECODE, ("Failed to decrypt buffer"),
("decryption failed %s", err->message));
g_error_free (err);
return GST_FLOW_ERROR;
}
tmp_buffer = hls_stream->pending_decrypted_buffer;
hls_stream->pending_decrypted_buffer = decrypted_buffer;
buffer = tmp_buffer;
if (!buffer)
return GST_FLOW_OK;
}
if (!hls_stream->pdt_tag_sent && file != NULL && file->datetime != NULL) {
GstDateTime *pdt_time = gst_date_time_new_from_g_date_time (g_date_time_ref
(file->datetime));
gst_adaptive_demux2_stream_set_tags (stream,
gst_tag_list_new (GST_TAG_DATE_TIME, pdt_time, NULL));
gst_date_time_unref (pdt_time);
hls_stream->pdt_tag_sent = TRUE;
}
return gst_hls_demux_stream_handle_buffer (stream, buffer, FALSE);
}
static void
gst_hls_demux_stream_finalize (GObject * object)
{
GstAdaptiveDemux2Stream *stream = (GstAdaptiveDemux2Stream *) object;
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (object);
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
if (hls_stream == hlsdemux->main_stream)
hlsdemux->main_stream = NULL;
g_free (hls_stream->lang);
g_free (hls_stream->name);
if (hls_stream->playlist) {
gst_hls_media_playlist_unref (hls_stream->playlist);
hls_stream->playlist = NULL;
}
if (hls_stream->init_file) {
gst_m3u8_init_file_unref (hls_stream->init_file);
hls_stream->init_file = NULL;
}
if (hls_stream->pending_encrypted_data)
g_object_unref (hls_stream->pending_encrypted_data);
gst_buffer_replace (&hls_stream->pending_decrypted_buffer, NULL);
gst_buffer_replace (&hls_stream->pending_typefind_buffer, NULL);
gst_buffer_replace (&hls_stream->pending_segment_data, NULL);
if (hls_stream->playlistloader) {
gst_hls_demux_playlist_loader_stop (hls_stream->playlistloader);
gst_object_unparent (GST_OBJECT (hls_stream->playlistloader));
gst_object_unref (hls_stream->playlistloader);
}
if (hls_stream->preloader) {
gst_hls_demux_preloader_free (hls_stream->preloader);
hls_stream->preloader = NULL;
}
if (hls_stream->moov)
gst_isoff_moov_box_free (hls_stream->moov);
if (hls_stream->current_key) {
g_free (hls_stream->current_key);
hls_stream->current_key = NULL;
}
if (hls_stream->current_iv) {
g_free (hls_stream->current_iv);
hls_stream->current_iv = NULL;
}
if (hls_stream->current_rendition) {
gst_hls_rendition_stream_unref (hls_stream->current_rendition);
hls_stream->current_rendition = NULL;
}
if (hls_stream->pending_rendition) {
gst_hls_rendition_stream_unref (hls_stream->pending_rendition);
hls_stream->pending_rendition = NULL;
}
if (hls_stream->current_segment) {
gst_m3u8_media_segment_unref (hls_stream->current_segment);
hls_stream->current_segment = NULL;
}
gst_hls_demux_stream_decrypt_end (hls_stream);
G_OBJECT_CLASS (stream_parent_class)->finalize (object);
}
static gboolean
gst_hls_demux_stream_has_next_fragment (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemuxStream *hls_stream = (GstHLSDemuxStream *) stream;
GST_DEBUG_OBJECT (stream, "has next ?");
if (hls_stream->current_segment == NULL)
return FALSE;
return gst_hls_media_playlist_has_next_fragment (hls_stream->playlist,
hls_stream->current_segment, stream->demux->segment.rate > 0);
}
static GstFlowReturn
gst_hls_demux_stream_advance_fragment (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemuxStream *hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GstM3U8MediaSegment *new_segment = NULL;
/* If we're playing partial segments, we need to continue
* doing that. We can only swap back to a full segment on a
* segment boundary */
if (hlsdemux_stream->in_partial_segments) {
/* Check if there's another partial segment in this fragment */
GstM3U8MediaSegment *cur_segment = hlsdemux_stream->current_segment;
guint avail_segments =
cur_segment->partial_segments !=
NULL ? cur_segment->partial_segments->len : 0;
if (hlsdemux_stream->part_idx + 1 < avail_segments) {
/* Advance to the next partial segment */
hlsdemux_stream->part_idx += 1;
GstM3U8PartialSegment *part =
g_ptr_array_index (cur_segment->partial_segments,
hlsdemux_stream->part_idx);
GST_DEBUG_OBJECT (stream,
"Advanced to partial segment sn:%" G_GINT64_FORMAT
" part %d stream_time:%" GST_STIME_FORMAT " uri:%s",
hlsdemux_stream->current_segment->sequence, hlsdemux_stream->part_idx,
GST_STIME_ARGS (part->stream_time), GST_STR_NULL (part->uri));
return GST_FLOW_OK;
} else if (cur_segment->partial_only) {
/* There's no partial segment available, because we're at the live edge */
GST_DEBUG_OBJECT (stream,
"Hit live edge playing partial segments. Will wait for playlist update.");
hlsdemux_stream->part_idx += 1;
return GST_FLOW_OK;
} else {
/* At the end of the partial segments for this full segment. Advance to the next full segment */
hlsdemux_stream->in_partial_segments = FALSE;
GST_DEBUG_OBJECT (stream,
"No more partial segments in current segment. Advancing");
}
}
GST_DEBUG_OBJECT (stream,
"Current segment sn:%" G_GINT64_FORMAT " stream_time:%" GST_STIME_FORMAT
" uri:%s", hlsdemux_stream->current_segment->sequence,
GST_STIME_ARGS (hlsdemux_stream->current_segment->stream_time),
GST_STR_NULL (hlsdemux_stream->current_segment->uri));
new_segment =
gst_hls_media_playlist_advance_fragment (hlsdemux_stream->playlist,
hlsdemux_stream->current_segment, stream->demux->segment.rate > 0);
if (new_segment) {
hlsdemux_stream->reset_pts = FALSE;
if (new_segment->discont_sequence !=
hlsdemux_stream->current_segment->discont_sequence)
gst_hls_demux_add_time_mapping (hlsdemux, new_segment->discont_sequence,
new_segment->stream_time, new_segment->datetime);
gst_m3u8_media_segment_unref (hlsdemux_stream->current_segment);
hlsdemux_stream->current_segment = new_segment;
/* In LL-HLS, handle advancing into the partial-only segment */
if (GST_HLS_MEDIA_PLAYLIST_IS_LIVE (hlsdemux_stream->playlist)
&& new_segment->partial_only) {
hlsdemux_stream->in_partial_segments = TRUE;
hlsdemux_stream->part_idx = 0;
GstM3U8PartialSegment *new_part =
g_ptr_array_index (new_segment->partial_segments,
hlsdemux_stream->part_idx);
GST_DEBUG_OBJECT (stream,
"Advanced to partial segment sn:%" G_GINT64_FORMAT
" part %u stream_time:%" GST_STIME_FORMAT " uri:%s",
new_segment->sequence, hlsdemux_stream->part_idx,
GST_STIME_ARGS (new_part->stream_time), GST_STR_NULL (new_part->uri));
return GST_FLOW_OK;
}
GST_DEBUG_OBJECT (stream,
"Advanced to segment sn:%" G_GINT64_FORMAT " stream_time:%"
GST_STIME_FORMAT " uri:%s", hlsdemux_stream->current_segment->sequence,
GST_STIME_ARGS (hlsdemux_stream->current_segment->stream_time),
GST_STR_NULL (hlsdemux_stream->current_segment->uri));
return GST_FLOW_OK;
}
GST_LOG_OBJECT (stream, "Could not advance to next fragment");
if (GST_HLS_MEDIA_PLAYLIST_IS_LIVE (hlsdemux_stream->playlist)) {
gst_m3u8_media_segment_unref (hlsdemux_stream->current_segment);
hlsdemux_stream->current_segment = NULL;
hlsdemux_stream->in_partial_segments = FALSE;
return GST_FLOW_OK;
}
return GST_FLOW_EOS;
}
static void
gst_hls_demux_stream_update_preloads (GstHLSDemuxStream * hlsdemux_stream)
{
GstHLSMediaPlaylist *playlist = hlsdemux_stream->playlist;
gboolean preloads_allowed = GST_HLS_MEDIA_PLAYLIST_IS_LIVE (playlist);
if (playlist->preload_hints == NULL || !preloads_allowed) {
if (hlsdemux_stream->preloader != NULL) {
/* Cancel any preloads, the new playlist doesn't have them */
gst_hls_demux_preloader_cancel (hlsdemux_stream->preloader,
M3U8_PRELOAD_HINT_ALL);
}
/* Nothing to preload */
return;
}
if (hlsdemux_stream->preloader == NULL) {
GstAdaptiveDemux *demux =
GST_ADAPTIVE_DEMUX2_STREAM (hlsdemux_stream)->demux;
hlsdemux_stream->preloader =
gst_hls_demux_preloader_new (demux->download_helper);
if (hlsdemux_stream->preloader == NULL) {
GST_WARNING_OBJECT (hlsdemux_stream, "Failed to create preload handler");
return;
}
}
/* The HLS spec says any extra preload hint of each type should be ignored */
GstM3U8PreloadHintType seen_types = 0;
guint idx;
for (idx = 0; idx < playlist->preload_hints->len; idx++) {
GstM3U8PreloadHint *hint = g_ptr_array_index (playlist->preload_hints, idx);
switch (hint->hint_type) {
case M3U8_PRELOAD_HINT_MAP:
case M3U8_PRELOAD_HINT_PART:
if (seen_types & hint->hint_type) {
continue; /* Ignore preload hint type we've already seen */
}
seen_types |= hint->hint_type;
break;
default:
GST_FIXME_OBJECT (hlsdemux_stream, "Ignoring unknown preload type %d",
hint->hint_type);
continue; /* Unknown hint type, ignore it */
}
gst_hls_demux_preloader_load (hlsdemux_stream->preloader, hint,
playlist->uri);
}
}
static GstFlowReturn
gst_hls_demux_stream_submit_request (GstAdaptiveDemux2Stream * stream,
DownloadRequest * download_req)
{
GstHLSDemuxStream *hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
/* See if the request can be satisfied from a preload */
if (hlsdemux_stream->preloader != NULL) {
if (gst_hls_demux_preloader_provide_request (hlsdemux_stream->preloader,
download_req))
return GST_FLOW_OK;
/* We're about to request something, but it wasn't the active preload,
* so make sure that's been stopped / cancelled so we're not downloading
* two things in parallel. This usually means the playlist refresh
* took too long and the preload became obsolete */
if (stream->downloading_header) {
gst_hls_demux_preloader_cancel (hlsdemux_stream->preloader,
M3U8_PRELOAD_HINT_MAP);
} else {
gst_hls_demux_preloader_cancel (hlsdemux_stream->preloader,
M3U8_PRELOAD_HINT_PART);
}
}
return
GST_ADAPTIVE_DEMUX2_STREAM_CLASS (stream_parent_class)->submit_request
(stream, download_req);
}
static void
gst_hls_demux_stream_handle_playlist_update (GstHLSDemuxStream * stream,
const gchar * new_playlist_uri, GstHLSMediaPlaylist * new_playlist)
{
GstHLSDemux *demux = GST_HLS_DEMUX_STREAM_GET_DEMUX (stream);
gboolean found_segment_discont = FALSE;
/* Synchronize playlist with previous one. If we can't update the playlist
* timing and inform the base class that we lost sync */
if (stream->playlist) {
if (!gst_hls_media_playlist_sync_to_playlist (new_playlist,
stream->playlist, &found_segment_discont)) {
/* Failure to synchronize with the previous media playlist is only fatal for
* variant streams. */
if (stream->is_variant) {
GST_DEBUG_OBJECT (stream,
"Could not synchronize new variant playlist with previous one !");
goto lost_sync;
}
/* For rendition streams, we can attempt synchronization against the
* variant playlist which is constantly updated */
if (demux->main_stream->playlist
&& !gst_hls_media_playlist_sync_to_playlist (new_playlist,
demux->main_stream->playlist, &found_segment_discont)) {
GST_DEBUG_OBJECT (stream,
"Could not do fallback synchronization of rendition stream to variant stream");
goto lost_sync;
}
}
} else {
found_segment_discont = TRUE;
if (!stream->is_variant && demux->main_stream->playlist) {
/* For initial rendition media playlist, attempt to synchronize the playlist
* against the variant stream. This is non-fatal if it fails. */
GST_DEBUG_OBJECT (stream,
"Attempting to synchronize initial rendition stream with variant stream");
gst_hls_media_playlist_sync_to_playlist (new_playlist,
demux->main_stream->playlist, NULL);
}
}
GST_DEBUG_OBJECT (stream, "Synchronized playlist. Update is discont : %d",
found_segment_discont);
if (found_segment_discont) {
stream->pending_discont = TRUE;
}
if (stream->current_segment) {
GstM3U8MediaSegment *new_segment;
GST_DEBUG_OBJECT (stream,
"Current segment sn:%" G_GINT64_FORMAT " stream_time:%" GST_STIME_FORMAT
" uri:%s", stream->current_segment->sequence,
GST_STIME_ARGS (stream->current_segment->stream_time),
GST_STR_NULL (stream->current_segment->uri));
/* 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
* since we will be checking the embedded timestamp later */
new_segment =
gst_hls_media_playlist_sync_to_segment (new_playlist,
stream->current_segment);
/* Handle LL-HLS partial segment sync by checking our partial segment
* still makes sense */
if (stream->in_partial_segments && new_segment) {
/* We must be either playing the trailing open-ended partial segment,
* or if we're playing partials from a complete segment, check that we
* still have a) partial segments attached (didn't get too old and
* the server removed them from the playlist) and b) we didn't advance
* beyond the end of that partial segment (when we advance past the live
* edge and increment part_idx, then the segment completes without
* adding any more partial segments) */
if (!new_segment->partial_only) {
if (new_segment->partial_segments == NULL) {
GST_DEBUG_OBJECT (stream,
"Partial segments we were playing became unavailable. Will try and resync");
stream->in_partial_segments = FALSE;
gst_m3u8_media_segment_unref (new_segment);
new_segment = NULL;
} else if (stream->part_idx >= new_segment->partial_segments->len) {
GST_DEBUG_OBJECT (stream,
"After playlist reload, there are no more partial segments to play in the current segment. Resyncing");
stream->in_partial_segments = FALSE;
gst_m3u8_media_segment_unref (new_segment);
new_segment = NULL;
}
}
}
if (new_segment) {
if (new_segment->discont_sequence !=
stream->current_segment->discont_sequence)
gst_hls_demux_add_time_mapping (demux, new_segment->discont_sequence,
new_segment->stream_time, new_segment->datetime);
/* This can happen in case of misaligned variants/renditions. Only warn about it */
if (new_segment->stream_time != stream->current_segment->stream_time)
GST_WARNING_OBJECT (stream,
"Returned segment stream time %" GST_STIME_FORMAT
" differs from current stream time %" GST_STIME_FORMAT,
GST_STIME_ARGS (new_segment->stream_time),
GST_STIME_ARGS (stream->current_segment->stream_time));
} else {
/* Not finding a matching segment only happens in live (otherwise we would
* have found a match by stream time) when we are at the live edge. This is normal*/
GST_DEBUG_OBJECT (stream, "Could not find a matching segment");
}
gst_m3u8_media_segment_unref (stream->current_segment);
stream->current_segment = new_segment;
} else {
GST_DEBUG_OBJECT (stream, "No current segment");
}
if (stream->is_variant) {
/* Updates on the variant playlist have some special requirements to
* set up the time mapping and initial stream config */
gst_hls_demux_handle_variant_playlist_update (demux, new_playlist_uri,
new_playlist);
} else if (stream->pending_rendition) {
/* Switching rendition configures a new playlist on the loader,
* and we should never get a callback for a stale download URI */
g_assert (!g_strcmp0 (stream->pending_rendition->uri, new_playlist_uri));
gst_hls_rendition_stream_unref (stream->current_rendition);
/* Stealing ref */
stream->current_rendition = stream->pending_rendition;
stream->pending_rendition = NULL;
}
if (stream->playlist)
gst_hls_media_playlist_unref (stream->playlist);
stream->playlist = gst_hls_media_playlist_ref (new_playlist);
stream->playlist_fetched = TRUE;
if (!GST_HLS_MEDIA_PLAYLIST_IS_LIVE (stream->playlist)) {
/* Make sure to cancel any preloads if a playlist isn't live after reload */
gst_hls_demux_stream_update_preloads (stream);
}
if (stream->current_segment) {
GST_DEBUG_OBJECT (stream,
"After update, current segment now sn:%" G_GINT64_FORMAT
" stream_time:%" GST_STIME_FORMAT " uri:%s",
stream->current_segment->sequence,
GST_STIME_ARGS (stream->current_segment->stream_time),
GST_STR_NULL (stream->current_segment->uri));
} else {
GST_DEBUG_OBJECT (stream, "No current segment selected");
}
GST_DEBUG_OBJECT (stream, "done");
return;
/* ERRORS */
lost_sync:
{
/* Set new playlist, lost sync handler will know what to do with it */
if (stream->playlist)
gst_hls_media_playlist_unref (stream->playlist);
stream->playlist = new_playlist;
stream->playlist = gst_hls_media_playlist_ref (new_playlist);
stream->playlist_fetched = TRUE;
stream->pending_discont = TRUE;
gst_hls_demux_reset_for_lost_sync (demux);
}
}
static void
on_playlist_update_success (GstHLSDemuxPlaylistLoader * pl,
const gchar * new_playlist_uri, GstHLSMediaPlaylist * new_playlist,
gpointer userdata)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (userdata);
gst_hls_demux_stream_handle_playlist_update (hls_stream,
new_playlist_uri, new_playlist);
gst_adaptive_demux2_stream_mark_prepared (GST_ADAPTIVE_DEMUX2_STREAM_CAST
(hls_stream));
}
static void
on_playlist_update_error (GstHLSDemuxPlaylistLoader * pl,
const gchar * playlist_uri, gpointer userdata)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (userdata);
/* FIXME: How to handle rendition playlist update errors? There's
* not much we can do about it except throw an error */
if (hls_stream->is_variant) {
GstHLSDemux *demux = GST_HLS_DEMUX_STREAM_GET_DEMUX (hls_stream);
gst_hls_demux_handle_variant_playlist_update_error (demux, playlist_uri);
} else {
GstHLSDemux *demux = GST_HLS_DEMUX_STREAM_GET_DEMUX (hls_stream);
GST_ELEMENT_ERROR (demux, STREAM, FAILED,
(_("Internal data stream error.")),
("Could not update rendition playlist"));
}
}
static GstHLSDemuxPlaylistLoader *
gst_hls_demux_stream_get_playlist_loader (GstHLSDemuxStream * hls_stream)
{
GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX2_STREAM_CAST (hls_stream)->demux;
if (hls_stream->playlistloader == NULL) {
hls_stream->playlistloader =
gst_hls_demux_playlist_loader_new (demux, demux->download_helper);
gst_hls_demux_playlist_loader_set_callbacks (hls_stream->playlistloader,
on_playlist_update_success, on_playlist_update_error, hls_stream);
}
return hls_stream->playlistloader;
}
void
gst_hls_demux_stream_set_playlist_uri (GstHLSDemuxStream * hls_stream,
gchar * uri)
{
GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX2_STREAM_CAST (hls_stream)->demux;
GstHLSDemuxPlaylistLoader *pl =
gst_hls_demux_stream_get_playlist_loader (hls_stream);
const gchar *main_uri = gst_adaptive_demux_get_manifest_ref_uri (demux);
gst_hls_demux_playlist_loader_set_playlist_uri (pl, main_uri, uri);
}
void
gst_hls_demux_stream_start_playlist_loading (GstHLSDemuxStream * hls_stream)
{
GstHLSDemuxPlaylistLoader *pl =
gst_hls_demux_stream_get_playlist_loader (hls_stream);
gst_hls_demux_playlist_loader_start (pl);
}
GstFlowReturn
gst_hls_demux_stream_check_current_playlist_uri (GstHLSDemuxStream * stream,
gchar * uri)
{
GstHLSDemuxPlaylistLoader *pl =
gst_hls_demux_stream_get_playlist_loader (stream);
if (!gst_hls_demux_playlist_loader_has_current_uri (pl, uri)) {
GST_LOG_OBJECT (stream, "Target playlist not available yet");
return GST_ADAPTIVE_DEMUX_FLOW_BUSY;
}
return GST_FLOW_OK;
#if 0
/* Check if a redirect happened */
if (g_strcmp0 (*uri, new_playlist->uri)) {
GST_DEBUG_OBJECT (stream, "Playlist URI update : '%s' => '%s'", *uri,
new_playlist->uri);
g_free (*uri);
*uri = g_strdup (new_playlist->uri);
}
#endif
}
static GstFlowReturn
gst_hls_demux_stream_update_fragment_info (GstAdaptiveDemux2Stream * stream)
{
GstFlowReturn ret = GST_FLOW_OK;
GstHLSDemuxStream *hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
GstAdaptiveDemux *demux = stream->demux;
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
GstM3U8MediaSegment *file;
GstM3U8PartialSegment *part = NULL;
gboolean discont;
/* Return BUSY if no playlist is loaded yet. Even if
* we switched an another playlist is loading, we'll keep*/
if (!hlsdemux_stream->playlist_fetched) {
gst_hls_demux_stream_start_playlist_loading (hlsdemux_stream);
return GST_ADAPTIVE_DEMUX_FLOW_BUSY;
}
g_assert (hlsdemux_stream->playlist != NULL);
if ((ret =
gst_hls_demux_stream_check_current_playlist_uri (hlsdemux_stream,
NULL)) != GST_FLOW_OK) {
/* The URI of the playlist we have is not the target URI due
* to a bitrate switch - wait for it to load */
GST_DEBUG_OBJECT (hlsdemux_stream,
"Playlist is stale. Waiting for new playlist");
gst_hls_demux_stream_start_playlist_loading (hlsdemux_stream);
return ret;
}
#ifndef GST_DISABLE_GST_DEBUG
GstClockTimeDiff live_edge_dist =
GST_CLOCK_TIME_IS_VALID (stream->current_position) ?
gst_hls_media_playlist_get_end_stream_time (hlsdemux_stream->playlist) -
stream->current_position : GST_CLOCK_TIME_NONE;
GstClockTime playlist_age =
gst_adaptive_demux2_get_monotonic_time (GST_ADAPTIVE_DEMUX (demux)) -
hlsdemux_stream->playlist->playlist_ts;
GST_DEBUG_OBJECT (stream,
"Updating fragment information, current_position:%" GST_TIME_FORMAT
" which is %" GST_STIME_FORMAT " from live edge. Playlist age %"
GST_TIME_FORMAT, GST_TIME_ARGS (stream->current_position),
GST_STIME_ARGS (live_edge_dist), GST_TIME_ARGS (playlist_age));
#endif
/* Find the current segment if we don't already have it */
if (hlsdemux_stream->current_segment == NULL) {
GST_LOG_OBJECT (stream, "No current segment");
if (stream->current_position == GST_CLOCK_TIME_NONE) {
GstM3U8SeekResult seek_result;
GST_DEBUG_OBJECT (stream, "Setting up initial segment");
if (gst_hls_media_playlist_get_starting_segment
(hlsdemux_stream->playlist, &seek_result)) {
hlsdemux_stream->current_segment = seek_result.segment;
hlsdemux_stream->in_partial_segments =
seek_result.found_partial_segment;
hlsdemux_stream->part_idx = seek_result.part_idx;
}
} else {
if (gst_hls_media_playlist_has_lost_sync (hlsdemux_stream->playlist,
stream->current_position)) {
GST_WARNING_OBJECT (stream, "Lost SYNC !");
return GST_ADAPTIVE_DEMUX_FLOW_LOST_SYNC;
}
GST_DEBUG_OBJECT (stream,
"Looking up segment for position %" GST_TIME_FORMAT,
GST_TIME_ARGS (stream->current_position));
GstM3U8SeekResult seek_result;
if (!gst_hls_media_playlist_find_position (hlsdemux_stream->playlist,
stream->current_position, hlsdemux_stream->in_partial_segments,
&seek_result)) {
GST_INFO_OBJECT (stream, "At the end of the current media playlist");
gst_hls_demux_stream_update_preloads (hlsdemux_stream);
return GST_FLOW_EOS;
}
hlsdemux_stream->current_segment = seek_result.segment;
hlsdemux_stream->in_partial_segments = seek_result.found_partial_segment;
hlsdemux_stream->part_idx = seek_result.part_idx;
/* If on a full segment, update time mapping. If it already exists it will be ignored.
* Don't add time mappings for partial segments, wait for a full segment boundary */
if (!hlsdemux_stream->in_partial_segments
|| hlsdemux_stream->part_idx == 0) {
gst_hls_demux_add_time_mapping (hlsdemux,
hlsdemux_stream->current_segment->discont_sequence,
hlsdemux_stream->current_segment->stream_time,
hlsdemux_stream->current_segment->datetime);
}
}
}
file = hlsdemux_stream->current_segment;
if (hlsdemux_stream->in_partial_segments) {
if (file->partial_segments == NULL) {
/* I think this can only happen if we reloaded the playlist
* and the segment we were in the middle of playing from
* removed its partial segments because we were playing
* too slowly */
GST_DEBUG_OBJECT (stream,
"Partial segment idx %d is not available in current playlist",
hlsdemux_stream->part_idx);
return GST_ADAPTIVE_DEMUX_FLOW_LOST_SYNC;
}
if (hlsdemux_stream->part_idx >= file->partial_segments->len) {
/* Being beyond the available partial segments in the partial_only
* segment at the end of the playlist in LL-HLS means we've
* hit the live edge and need to wait for a playlist update */
if (file->partial_only) {
GST_INFO_OBJECT (stream, "At the end of the current media playlist");
gst_hls_demux_stream_update_preloads (hlsdemux_stream);
return GST_FLOW_EOS;
}
/* Otherwise, we reloaded the playlist and found that the partial_only segment we
* were playing from became a real segment and we overstepped the end of
* the parts. Reloading the playlist should have synced that up properly,
* so we should never get here. */
g_assert_not_reached ();
}
part =
g_ptr_array_index (file->partial_segments, hlsdemux_stream->part_idx);
GST_DEBUG_OBJECT (stream,
"Current partial segment %d stream_time %" GST_STIME_FORMAT,
hlsdemux_stream->part_idx, GST_STIME_ARGS (part->stream_time));
discont = stream->discont;
/* Use the segment discont flag only on the first partial segment */
if ((hlsdemux_stream->pending_discont || file->discont)
&& hlsdemux_stream->part_idx == 0)
discont = TRUE;
} else {
GST_DEBUG_OBJECT (stream, "Current segment stream_time %" GST_STIME_FORMAT,
GST_STIME_ARGS (file->stream_time));
discont = file->discont || stream->discont
|| hlsdemux_stream->pending_discont;
}
gboolean need_header = GST_ADAPTIVE_DEMUX2_STREAM_NEED_HEADER (stream);
/* Check if the MAP header file changed and update it */
if (file->init_file != NULL
&& !gst_m3u8_init_file_equal (hlsdemux_stream->init_file,
file->init_file)) {
GST_DEBUG_OBJECT (stream, "MAP header info changed. Updating");
if (hlsdemux_stream->init_file != NULL)
gst_m3u8_init_file_unref (hlsdemux_stream->init_file);
hlsdemux_stream->init_file = gst_m3u8_init_file_ref (file->init_file);
need_header = TRUE;
}
if (file->init_file && need_header) {
GstM3U8InitFile *header_file = file->init_file;
g_free (stream->fragment.header_uri);
stream->fragment.header_uri = g_strdup (header_file->uri);
stream->fragment.header_range_start = header_file->offset;
if (header_file->size != -1) {
stream->fragment.header_range_end =
header_file->offset + header_file->size - 1;
} else {
stream->fragment.header_range_end = -1;
}
stream->need_header = TRUE;
GST_DEBUG_OBJECT (stream, "Need header uri: %s %" G_GUINT64_FORMAT " %"
G_GINT64_FORMAT, stream->fragment.header_uri,
stream->fragment.header_range_start, stream->fragment.header_range_end);
}
/* set up our source for download */
stream->fragment.stream_time = GST_CLOCK_STIME_NONE;
g_free (stream->fragment.uri);
stream->fragment.range_start = 0;
stream->fragment.range_end = -1;
/* Encryption params always come from the parent segment */
g_free (hlsdemux_stream->current_key);
hlsdemux_stream->current_key = g_strdup (file->key);
g_free (hlsdemux_stream->current_iv);
hlsdemux_stream->current_iv = g_memdup2 (file->iv, sizeof (file->iv));
/* Other info could come from the part when playing partial segments */
if (part == NULL) {
if (hlsdemux_stream->reset_pts || discont || demux->segment.rate < 0.0) {
stream->fragment.stream_time = file->stream_time;
}
stream->fragment.uri = g_strdup (file->uri);
stream->fragment.range_start = file->offset;
if (file->size != -1)
stream->fragment.range_end = file->offset + file->size - 1;
stream->fragment.duration = file->duration;
} else {
if (hlsdemux_stream->reset_pts || discont || demux->segment.rate < 0.0) {
stream->fragment.stream_time = part->stream_time;
}
stream->fragment.uri = g_strdup (part->uri);
stream->fragment.range_start = part->offset;
if (part->size != -1)
stream->fragment.range_end = part->offset + part->size - 1;
stream->fragment.duration = part->duration;
}
GST_DEBUG_OBJECT (stream, "Stream URI now %s", stream->fragment.uri);
stream->recommended_buffering_threshold =
gst_hls_media_playlist_recommended_buffering_threshold
(hlsdemux_stream->playlist);
if (discont)
stream->discont = TRUE;
hlsdemux_stream->pending_discont = FALSE;
return ret;
}
static gboolean
gst_hls_demux_stream_can_start (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GstHLSDemuxStream *hls_stream = (GstHLSDemuxStream *) stream;
GList *tmp;
GST_DEBUG_OBJECT (stream, "is_variant:%d mappings:%p", hls_stream->is_variant,
hlsdemux->mappings);
/* Variant streams can always start straight away */
if (hls_stream->is_variant)
return TRUE;
/* Renditions of the exact same type as the variant are pure alternatives,
* they must be started. This can happen for example with audio-only manifests
* where the initial stream selected is a rendition and not a variant */
if (hls_stream->rendition_type == hlsdemux->main_stream->rendition_type)
return TRUE;
/* Rendition streams only require delaying if we don't have time mappings yet */
if (!hlsdemux->mappings)
return FALSE;
/* We can start if we have at least one internal time observation */
for (tmp = hlsdemux->mappings; tmp; tmp = tmp->next) {
GstHLSTimeMap *map = tmp->data;
if (map->internal_time != GST_CLOCK_TIME_NONE)
return TRUE;
}
/* Otherwise we have to wait */
return FALSE;
}
static void
gst_hls_demux_stream_start (GstAdaptiveDemux2Stream * stream)
{
if (!gst_hls_demux_stream_can_start (stream))
return;
/* Start the playlist loader */
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
gst_hls_demux_stream_start_playlist_loading (hls_stream);
/* Chain up, to start the downloading */
GST_ADAPTIVE_DEMUX2_STREAM_CLASS (stream_parent_class)->start (stream);
}
static void
gst_hls_demux_stream_stop (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
if (hls_stream->playlistloader && !hls_stream->is_variant) {
/* Don't stop the loader for the variant stream, keep it running
* until the scheduler itself is stopped so we keep updating
* the live playlist timeline */
gst_hls_demux_playlist_loader_stop (hls_stream->playlistloader);
}
/* Chain up, to stop the downloading */
GST_ADAPTIVE_DEMUX2_STREAM_CLASS (stream_parent_class)->stop (stream);
}
/* Called when the variant is changed, to set a new rendition
* for this stream to download. Returns TRUE if the rendition
* stream switched group-id */
static gboolean
gst_hls_demux_update_rendition_stream_uri (GstHLSDemux * hlsdemux,
GstHLSDemuxStream * hls_stream, GError ** err)
{
gchar *current_group_id, *requested_group_id;
GstHLSRenditionStream *replacement_media = NULL;
GList *tmp;
/* There always should be a current variant set */
g_assert (hlsdemux->current_variant);
/* There always is a GstHLSRenditionStream set for rendition streams */
g_assert (hls_stream->current_rendition);
requested_group_id =
hlsdemux->current_variant->media_groups[hls_stream->
current_rendition->mtype];
current_group_id = hls_stream->current_rendition->group_id;
GST_DEBUG_OBJECT (hlsdemux,
"Checking playlist change for variant stream %s lang: %s current group-id: %s / requested group-id: %s",
gst_stream_type_get_name (hls_stream->rendition_type), hls_stream->lang,
current_group_id, requested_group_id);
if (!g_strcmp0 (requested_group_id, current_group_id)) {
GST_DEBUG_OBJECT (hlsdemux, "No change needed");
return FALSE;
}
GST_DEBUG_OBJECT (hlsdemux,
"group-id changed, looking for replacement playlist");
/* Need to switch/update */
for (tmp = hlsdemux->master->renditions; tmp; tmp = tmp->next) {
GstHLSRenditionStream *cand = tmp->data;
if (cand->mtype == hls_stream->current_rendition->mtype
&& !g_strcmp0 (cand->lang, hls_stream->lang)
&& !g_strcmp0 (cand->group_id, requested_group_id)) {
replacement_media = cand;
break;
}
}
if (!replacement_media) {
GST_ERROR_OBJECT (hlsdemux,
"Could not find a replacement playlist. Staying with previous one");
return FALSE;
}
GST_DEBUG_OBJECT (hlsdemux, "Use replacement playlist %s",
replacement_media->name);
if (hls_stream->pending_rendition) {
GST_ERROR_OBJECT (hlsdemux,
"Already had a pending rendition switch to '%s'",
hls_stream->pending_rendition->name);
gst_hls_rendition_stream_unref (hls_stream->pending_rendition);
}
hls_stream->pending_rendition =
gst_hls_rendition_stream_ref (replacement_media);
gst_hls_demux_stream_set_playlist_uri (hls_stream, replacement_media->uri);
return TRUE;
}
static gboolean
gst_hls_demux_stream_select_bitrate (GstAdaptiveDemux2Stream * stream,
guint64 bitrate)
{
GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (stream->demux);
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
GstHLSDemuxStream *hls_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
/* Fast-Path, no changes possible */
if (hlsdemux->master == NULL || hlsdemux->master->is_simple)
return FALSE;
/* Currently playing partial segments, disallow bitrate
* switches and rendition playlist changes - except exactly
* at the first partial segment in a full segment (implying
* we are about to play a partial segment but didn't yet) */
if (hls_stream->in_partial_segments && hls_stream->part_idx > 0)
return FALSE;
if (hls_stream->is_variant) {
gdouble play_rate = gst_adaptive_demux_play_rate (demux);
gboolean changed = FALSE;
/* If not calculated yet, continue using start bitrate */
if (bitrate == 0)
bitrate = hlsdemux->start_bitrate;
/* Handle variant streams */
GST_DEBUG_OBJECT (hlsdemux,
"Checking playlist change for main variant stream");
if (!gst_hls_demux_change_variant_playlist (hlsdemux,
hlsdemux->current_variant->iframe,
bitrate / MAX (1.0, ABS (play_rate)), &changed)) {
GST_ERROR_OBJECT (hlsdemux, "Failed to choose a new variant to play");
}
GST_DEBUG_OBJECT (hlsdemux, "Returning changed: %d", changed);
return changed;
}
/* Handle rendition streams */
return gst_hls_demux_update_rendition_stream_uri (hlsdemux, hls_stream, NULL);
}
#if defined(HAVE_OPENSSL)
static gboolean
gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream,
const guint8 * key_data, const guint8 * iv_data)
{
EVP_CIPHER_CTX *ctx;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_init (&stream->aes_ctx);
ctx = &stream->aes_ctx;
#else
stream->aes_ctx = EVP_CIPHER_CTX_new ();
ctx = stream->aes_ctx;
#endif
if (!EVP_DecryptInit_ex (ctx, EVP_aes_128_cbc (), NULL, key_data, iv_data))
return FALSE;
EVP_CIPHER_CTX_set_padding (ctx, 0);
return TRUE;
}
static gboolean
decrypt_fragment (GstHLSDemuxStream * stream, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data)
{
int len, flen = 0;
EVP_CIPHER_CTX *ctx;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ctx = &stream->aes_ctx;
#else
ctx = stream->aes_ctx;
#endif
if (G_UNLIKELY (length > G_MAXINT || length % 16 != 0))
return FALSE;
len = (int) length;
if (!EVP_DecryptUpdate (ctx, decrypted_data, &len, encrypted_data, len))
return FALSE;
EVP_DecryptFinal_ex (ctx, decrypted_data + len, &flen);
g_return_val_if_fail (len + flen == length, FALSE);
return TRUE;
}
static void
gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_CIPHER_CTX_cleanup (&stream->aes_ctx);
#else
EVP_CIPHER_CTX_free (stream->aes_ctx);
stream->aes_ctx = NULL;
#endif
}
#elif defined(HAVE_NETTLE)
static gboolean
gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream,
const guint8 * key_data, const guint8 * iv_data)
{
aes128_set_decrypt_key (&stream->aes_ctx.ctx, key_data);
CBC_SET_IV (&stream->aes_ctx, iv_data);
return TRUE;
}
static gboolean
decrypt_fragment (GstHLSDemuxStream * stream, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data)
{
if (length % 16 != 0)
return FALSE;
CBC_DECRYPT (&stream->aes_ctx, aes128_decrypt, length, decrypted_data,
encrypted_data);
return TRUE;
}
static void
gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream)
{
/* NOP */
}
#elif defined(HAVE_LIBGCRYPT)
static gboolean
gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream,
const guint8 * key_data, const guint8 * iv_data)
{
gcry_error_t err = 0;
gboolean ret = FALSE;
err =
gcry_cipher_open (&stream->aes_ctx, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_CBC, 0);
if (err)
goto out;
err = gcry_cipher_setkey (stream->aes_ctx, key_data, 16);
if (err)
goto out;
err = gcry_cipher_setiv (stream->aes_ctx, iv_data, 16);
if (!err)
ret = TRUE;
out:
if (!ret)
if (stream->aes_ctx)
gcry_cipher_close (stream->aes_ctx);
return ret;
}
static gboolean
decrypt_fragment (GstHLSDemuxStream * stream, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data)
{
gcry_error_t err = 0;
err = gcry_cipher_decrypt (stream->aes_ctx, decrypted_data, length,
encrypted_data, length);
return err == 0;
}
static void
gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream)
{
if (stream->aes_ctx) {
gcry_cipher_close (stream->aes_ctx);
stream->aes_ctx = NULL;
}
}
#else
/* NO crypto available */
static gboolean
gst_hls_demux_stream_decrypt_start (GstHLSDemuxStream * stream,
const guint8 * key_data, const guint8 * iv_data)
{
GST_ERROR ("No crypto available");
return FALSE;
}
static gboolean
decrypt_fragment (GstHLSDemuxStream * stream, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data)
{
GST_ERROR ("Cannot decrypt fragment, no crypto available");
return FALSE;
}
static void
gst_hls_demux_stream_decrypt_end (GstHLSDemuxStream * stream)
{
return;
}
#endif
static GstBuffer *
gst_hls_demux_decrypt_fragment (GstHLSDemux * demux, GstHLSDemuxStream * stream,
GstBuffer * encrypted_buffer, GError ** err)
{
GstBuffer *decrypted_buffer = NULL;
GstMapInfo encrypted_info, decrypted_info;
decrypted_buffer =
gst_buffer_new_allocate (NULL, gst_buffer_get_size (encrypted_buffer),
NULL);
gst_buffer_map (encrypted_buffer, &encrypted_info, GST_MAP_READ);
gst_buffer_map (decrypted_buffer, &decrypted_info, GST_MAP_WRITE);
if (!decrypt_fragment (stream, encrypted_info.size,
encrypted_info.data, decrypted_info.data))
goto decrypt_error;
gst_buffer_unmap (decrypted_buffer, &decrypted_info);
gst_buffer_unmap (encrypted_buffer, &encrypted_info);
gst_buffer_unref (encrypted_buffer);
return decrypted_buffer;
decrypt_error:
GST_ERROR_OBJECT (demux, "Failed to decrypt fragment");
g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_DECRYPT,
"Failed to decrypt fragment");
gst_buffer_unmap (decrypted_buffer, &decrypted_info);
gst_buffer_unmap (encrypted_buffer, &encrypted_info);
gst_buffer_unref (encrypted_buffer);
gst_buffer_unref (decrypted_buffer);
return NULL;
}
static GstClockTime
gst_hls_demux_stream_get_presentation_offset (GstAdaptiveDemux2Stream * stream)
{
GstHLSDemux *hlsdemux = (GstHLSDemux *) stream->demux;
GstHLSDemuxStream *hls_stream = (GstHLSDemuxStream *) stream;
GST_DEBUG_OBJECT (stream, "presentation_offset %" GST_TIME_FORMAT,
GST_TIME_ARGS (hls_stream->presentation_offset));
/* If this stream and the variant stream are ISOBMFF, returns the presentation
* offset of the variant stream */
if (hls_stream->parser_type == GST_HLS_PARSER_ISOBMFF
&& hlsdemux->main_stream->parser_type == GST_HLS_PARSER_ISOBMFF)
return hlsdemux->main_stream->presentation_offset;
return hls_stream->presentation_offset;
}