mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
8d86a8b75d
For live streams, we want to make sure there's a certain distance between the sequence to play and the last (earliest) fragment. The problem is that it assumes there are at least 3 fragments in the playlist, which might not always be the case (like in the case of a server restarting and gradually adding fragments). In order to avoid ending up with negative sequence numbers (which will just loop forever), limit the new target sequence number to the highest of: * either the first sequence number of the playlist (fallback) * or 3 fragments from the last one (standard behaviour)
1284 lines
39 KiB
C
1284 lines
39 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>
|
|
*
|
|
* Gsthlsdemux.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.
|
|
*/
|
|
/**
|
|
* SECTION:element-hlsdemux
|
|
*
|
|
* HTTP Live Streaming demuxer element.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 souphttpsrc location=http://devimages.apple.com/iphone/samples/bipbop/gear4/prog_index.m3u8 ! hlsdemux ! decodebin ! videoconvert ! videoscale ! autovideosink
|
|
* ]|
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <gst/base/gsttypefindhelper.h>
|
|
#include "gsthlsdemux.h"
|
|
|
|
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src_%u",
|
|
GST_PAD_SRC,
|
|
GST_PAD_SOMETIMES,
|
|
GST_STATIC_CAPS_ANY);
|
|
|
|
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-hls"));
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_hls_demux_debug);
|
|
#define GST_CAT_DEFAULT gst_hls_demux_debug
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_FRAGMENTS_CACHE,
|
|
PROP_LAST
|
|
};
|
|
|
|
#define DEFAULT_FRAGMENTS_CACHE 1
|
|
|
|
/* GObject */
|
|
static void gst_hls_demux_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_hls_demux_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_hls_demux_dispose (GObject * obj);
|
|
|
|
/* GstElement */
|
|
static GstStateChangeReturn
|
|
gst_hls_demux_change_state (GstElement * element, GstStateChange transition);
|
|
|
|
/* GstHLSDemux */
|
|
static gboolean gst_hls_demux_update_playlist (GstHLSDemux * demux,
|
|
gboolean update, GError ** err);
|
|
static gboolean gst_hls_demux_set_location (GstHLSDemux * demux,
|
|
const gchar * uri, const gchar * base_uri);
|
|
static gchar *gst_hls_src_buf_to_utf8_playlist (GstBuffer * buf);
|
|
|
|
static gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux,
|
|
guint max_bitrate, gboolean * changed);
|
|
static GstBuffer *gst_hls_demux_decrypt_fragment (GstHLSDemux * demux,
|
|
GstBuffer * encrypted_buffer, GError ** err);
|
|
static gboolean
|
|
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
|
|
const guint8 * iv_data);
|
|
static void gst_hls_demux_decrypt_end (GstHLSDemux * demux);
|
|
|
|
static gboolean gst_hls_demux_is_live (GstAdaptiveDemux * demux);
|
|
static GstClockTime gst_hls_demux_get_duration (GstAdaptiveDemux * demux);
|
|
static gint64 gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux *
|
|
demux);
|
|
static gboolean gst_hls_demux_process_manifest (GstAdaptiveDemux * demux,
|
|
GstBuffer * buf);
|
|
static GstFlowReturn gst_hls_demux_update_manifest (GstAdaptiveDemux * demux);
|
|
static gboolean gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek);
|
|
static gboolean
|
|
gst_hls_demux_start_fragment (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream);
|
|
static GstFlowReturn gst_hls_demux_finish_fragment (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream);
|
|
static GstFlowReturn gst_hls_demux_data_received (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream);
|
|
static gboolean gst_hls_demux_stream_has_next_fragment (GstAdaptiveDemuxStream *
|
|
stream);
|
|
static GstFlowReturn gst_hls_demux_advance_fragment (GstAdaptiveDemuxStream *
|
|
stream);
|
|
static GstFlowReturn gst_hls_demux_update_fragment_info (GstAdaptiveDemuxStream
|
|
* stream);
|
|
static gboolean gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream,
|
|
guint64 bitrate);
|
|
static void gst_hls_demux_reset (GstAdaptiveDemux * demux);
|
|
static gboolean gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux,
|
|
gint64 * start, gint64 * stop);
|
|
|
|
#define gst_hls_demux_parent_class parent_class
|
|
G_DEFINE_TYPE (GstHLSDemux, gst_hls_demux, GST_TYPE_ADAPTIVE_DEMUX);
|
|
|
|
static void
|
|
gst_hls_demux_dispose (GObject * obj)
|
|
{
|
|
GstHLSDemux *demux = GST_HLS_DEMUX (obj);
|
|
|
|
gst_hls_demux_reset (GST_ADAPTIVE_DEMUX_CAST (demux));
|
|
gst_m3u8_client_free (demux->client);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (obj);
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_class_init (GstHLSDemuxClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *element_class;
|
|
GstAdaptiveDemuxClass *adaptivedemux_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
element_class = (GstElementClass *) klass;
|
|
adaptivedemux_class = (GstAdaptiveDemuxClass *) klass;
|
|
|
|
gobject_class->set_property = gst_hls_demux_set_property;
|
|
gobject_class->get_property = gst_hls_demux_get_property;
|
|
gobject_class->dispose = gst_hls_demux_dispose;
|
|
|
|
#ifndef GST_REMOVE_DEPRECATED
|
|
g_object_class_install_property (gobject_class, PROP_FRAGMENTS_CACHE,
|
|
g_param_spec_uint ("fragments-cache", "Fragments cache",
|
|
"Number of fragments needed to be cached to start playing "
|
|
"(DEPRECATED: Has no effect since 1.3.1)",
|
|
1, G_MAXUINT, DEFAULT_FRAGMENTS_CACHE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED));
|
|
#endif
|
|
|
|
element_class->change_state = GST_DEBUG_FUNCPTR (gst_hls_demux_change_state);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&srctemplate));
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&sinktemplate));
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"HLS Demuxer",
|
|
"Codec/Demuxer/Adaptive",
|
|
"HTTP Live Streaming demuxer",
|
|
"Marc-Andre Lureau <marcandre.lureau@gmail.com>\n"
|
|
"Andoni Morales Alastruey <ylatuya@gmail.com>");
|
|
|
|
adaptivedemux_class->is_live = gst_hls_demux_is_live;
|
|
adaptivedemux_class->get_live_seek_range = gst_hls_demux_get_live_seek_range;
|
|
adaptivedemux_class->get_duration = gst_hls_demux_get_duration;
|
|
adaptivedemux_class->get_manifest_update_interval =
|
|
gst_hls_demux_get_manifest_update_interval;
|
|
adaptivedemux_class->process_manifest = gst_hls_demux_process_manifest;
|
|
adaptivedemux_class->update_manifest = gst_hls_demux_update_manifest;
|
|
adaptivedemux_class->reset = gst_hls_demux_reset;
|
|
adaptivedemux_class->seek = gst_hls_demux_seek;
|
|
adaptivedemux_class->stream_has_next_fragment =
|
|
gst_hls_demux_stream_has_next_fragment;
|
|
adaptivedemux_class->stream_advance_fragment = gst_hls_demux_advance_fragment;
|
|
adaptivedemux_class->stream_update_fragment_info =
|
|
gst_hls_demux_update_fragment_info;
|
|
adaptivedemux_class->stream_select_bitrate = gst_hls_demux_select_bitrate;
|
|
|
|
adaptivedemux_class->start_fragment = gst_hls_demux_start_fragment;
|
|
adaptivedemux_class->finish_fragment = gst_hls_demux_finish_fragment;
|
|
adaptivedemux_class->data_received = gst_hls_demux_data_received;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_hls_demux_debug, "hlsdemux", 0,
|
|
"hlsdemux element");
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_init (GstHLSDemux * demux)
|
|
{
|
|
demux->do_typefind = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
switch (prop_id) {
|
|
case PROP_FRAGMENTS_CACHE:
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
switch (prop_id) {
|
|
case PROP_FRAGMENTS_CACHE:
|
|
g_value_set_uint (value, 1);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_hls_demux_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
GstHLSDemux *demux = GST_HLS_DEMUX (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
gst_hls_demux_reset (GST_ADAPTIVE_DEMUX_CAST (demux));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_hls_demux_reset (GST_ADAPTIVE_DEMUX_CAST (demux));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static GstPad *
|
|
gst_hls_demux_create_pad (GstHLSDemux * hlsdemux)
|
|
{
|
|
gchar *name;
|
|
GstPadTemplate *tmpl;
|
|
GstPad *pad;
|
|
|
|
name = g_strdup_printf ("src_%u", hlsdemux->srcpad_counter++);
|
|
tmpl = gst_static_pad_template_get (&srctemplate);
|
|
pad = gst_ghost_pad_new_no_target_from_template (name, tmpl);
|
|
gst_object_unref (tmpl);
|
|
g_free (name);
|
|
|
|
return pad;
|
|
}
|
|
|
|
static guint64
|
|
gst_hls_demux_get_bitrate (GstHLSDemux * hlsdemux)
|
|
{
|
|
GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (hlsdemux);
|
|
|
|
/* Valid because hlsdemux only has a single output */
|
|
if (demux->streams) {
|
|
GstAdaptiveDemuxStream *stream = demux->streams->data;
|
|
return stream->current_download_rate;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_seek (GstAdaptiveDemux * demux, GstEvent * seek)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
GstFormat format;
|
|
GstSeekFlags flags;
|
|
GstSeekType start_type, stop_type;
|
|
gint64 start, stop;
|
|
gdouble rate;
|
|
GList *walk, *current_file = NULL;
|
|
GstClockTime current_pos, target_pos;
|
|
gint64 current_sequence;
|
|
GstM3U8MediaFile *file;
|
|
guint64 bitrate;
|
|
|
|
gst_event_parse_seek (seek, &rate, &format, &flags, &start_type, &start,
|
|
&stop_type, &stop);
|
|
|
|
bitrate = gst_hls_demux_get_bitrate (hlsdemux);
|
|
|
|
/* properly cleanup pending decryption status */
|
|
if (flags & GST_SEEK_FLAG_FLUSH) {
|
|
gst_hls_demux_decrypt_end (hlsdemux);
|
|
}
|
|
|
|
/* Use I-frame variants for trick modes */
|
|
if (hlsdemux->client->main->iframe_lists && rate < -1.0
|
|
&& demux->segment.rate >= -1.0 && demux->segment.rate <= 1.0) {
|
|
GError *err = NULL;
|
|
|
|
GST_M3U8_CLIENT_LOCK (hlsdemux->client);
|
|
/* Switch to I-frame variant */
|
|
hlsdemux->client->main->current_variant =
|
|
hlsdemux->client->main->iframe_lists;
|
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
|
gst_m3u8_client_set_current (hlsdemux->client,
|
|
hlsdemux->client->main->iframe_lists->data);
|
|
gst_uri_downloader_reset (demux->downloader);
|
|
if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) {
|
|
GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err);
|
|
return FALSE;
|
|
}
|
|
//hlsdemux->discont = TRUE;
|
|
hlsdemux->do_typefind = TRUE;
|
|
|
|
gst_hls_demux_change_playlist (hlsdemux, bitrate / ABS (rate), NULL);
|
|
} else if (rate > -1.0 && rate <= 1.0 && (demux->segment.rate < -1.0
|
|
|| demux->segment.rate > 1.0)) {
|
|
GError *err = NULL;
|
|
GST_M3U8_CLIENT_LOCK (hlsdemux->client);
|
|
/* Switch to normal variant */
|
|
hlsdemux->client->main->current_variant = hlsdemux->client->main->lists;
|
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
|
gst_m3u8_client_set_current (hlsdemux->client,
|
|
hlsdemux->client->main->lists->data);
|
|
gst_uri_downloader_reset (demux->downloader);
|
|
if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) {
|
|
GST_ELEMENT_ERROR_FROM_ERROR (hlsdemux, "Could not switch playlist", err);
|
|
return FALSE;
|
|
}
|
|
//hlsdemux->discont = TRUE;
|
|
hlsdemux->do_typefind = TRUE;
|
|
/* TODO why not continue using the same? that was being used up to now? */
|
|
gst_hls_demux_change_playlist (hlsdemux, bitrate, NULL);
|
|
}
|
|
|
|
GST_M3U8_CLIENT_LOCK (hlsdemux->client);
|
|
file = GST_M3U8_MEDIA_FILE (hlsdemux->client->current->files->data);
|
|
current_sequence = file->sequence;
|
|
current_pos = 0;
|
|
target_pos = rate > 0 ? start : stop;
|
|
/* FIXME: Here we need proper discont handling */
|
|
for (walk = hlsdemux->client->current->files; walk; walk = walk->next) {
|
|
file = walk->data;
|
|
|
|
current_sequence = file->sequence;
|
|
current_file = walk;
|
|
if (current_pos <= target_pos && target_pos < current_pos + file->duration) {
|
|
break;
|
|
}
|
|
current_pos += file->duration;
|
|
}
|
|
|
|
if (walk == NULL) {
|
|
GST_DEBUG_OBJECT (demux, "seeking further than track duration");
|
|
current_sequence++;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux, "seeking to sequence %u", (guint) current_sequence);
|
|
hlsdemux->reset_pts = TRUE;
|
|
hlsdemux->client->sequence = current_sequence;
|
|
hlsdemux->client->current_file =
|
|
current_file ? current_file : hlsdemux->client->current->files;
|
|
hlsdemux->client->sequence_position = current_pos;
|
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_hls_demux_update_manifest (GstAdaptiveDemux * demux)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
if (!gst_hls_demux_update_playlist (hlsdemux, TRUE, NULL))
|
|
return GST_FLOW_ERROR;
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_setup_streams (GstAdaptiveDemux * demux)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
/* only 1 output supported */
|
|
gst_adaptive_demux_stream_new (demux, gst_hls_demux_create_pad (hlsdemux));
|
|
|
|
hlsdemux->reset_pts = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gst_hls_demux_process_manifest (GstAdaptiveDemux * demux, GstBuffer * buf)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
gchar *playlist = NULL;
|
|
|
|
gst_hls_demux_set_location (hlsdemux, demux->manifest_uri,
|
|
demux->manifest_base_uri);
|
|
|
|
playlist = gst_hls_src_buf_to_utf8_playlist (buf);
|
|
if (playlist == NULL) {
|
|
GST_WARNING_OBJECT (demux, "Error validating first playlist.");
|
|
return FALSE;
|
|
} else if (!gst_m3u8_client_update (hlsdemux->client, playlist)) {
|
|
/* In most cases, this will happen if we set a wrong url in the
|
|
* source element and we have received the 404 HTML response instead of
|
|
* the playlist */
|
|
GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Invalid playlist."), (NULL));
|
|
return FALSE;
|
|
}
|
|
|
|
/* If this playlist is a variant playlist, select the first one
|
|
* and update it */
|
|
if (gst_m3u8_client_has_variant_playlist (hlsdemux->client)) {
|
|
GstM3U8 *child = NULL;
|
|
GError *err = NULL;
|
|
|
|
if (demux->connection_speed == 0) {
|
|
GST_M3U8_CLIENT_LOCK (hlsdemux->client);
|
|
child = hlsdemux->client->main->current_variant->data;
|
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
|
} else {
|
|
GList *tmp = gst_m3u8_client_get_playlist_for_bitrate (hlsdemux->client,
|
|
demux->connection_speed);
|
|
|
|
child = GST_M3U8 (tmp->data);
|
|
}
|
|
|
|
gst_m3u8_client_set_current (hlsdemux->client, child);
|
|
if (!gst_hls_demux_update_playlist (hlsdemux, FALSE, &err)) {
|
|
GST_ELEMENT_ERROR_FROM_ERROR (demux, "Could not fetch the child playlist",
|
|
err);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return gst_hls_demux_setup_streams (demux);
|
|
}
|
|
|
|
static GstClockTime
|
|
gst_hls_demux_get_duration (GstAdaptiveDemux * demux)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
return gst_m3u8_client_get_duration (hlsdemux->client);
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_is_live (GstAdaptiveDemux * demux)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
return gst_m3u8_client_is_live (hlsdemux->client);
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_start_fragment (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
if (hlsdemux->current_key) {
|
|
GError *err = NULL;
|
|
GstFragment *key_fragment;
|
|
GstBuffer *key_buffer;
|
|
GstMapInfo key_info;
|
|
|
|
/* new key? */
|
|
if (hlsdemux->key_url
|
|
&& strcmp (hlsdemux->key_url, hlsdemux->current_key) == 0) {
|
|
key_fragment = g_object_ref (hlsdemux->key_fragment);
|
|
} else {
|
|
g_free (hlsdemux->key_url);
|
|
hlsdemux->key_url = NULL;
|
|
|
|
if (hlsdemux->key_fragment)
|
|
g_object_unref (hlsdemux->key_fragment);
|
|
hlsdemux->key_fragment = NULL;
|
|
|
|
GST_INFO_OBJECT (demux, "Fetching key %s", hlsdemux->current_key);
|
|
key_fragment =
|
|
gst_uri_downloader_fetch_uri (demux->downloader,
|
|
hlsdemux->current_key, hlsdemux->client->main ?
|
|
hlsdemux->client->main->uri : NULL, FALSE, FALSE,
|
|
hlsdemux->client->current ? hlsdemux->client->current->
|
|
allowcache : TRUE, &err);
|
|
if (key_fragment == NULL)
|
|
goto key_failed;
|
|
hlsdemux->key_url = g_strdup (hlsdemux->current_key);
|
|
hlsdemux->key_fragment = g_object_ref (key_fragment);
|
|
}
|
|
|
|
key_buffer = gst_fragment_get_buffer (key_fragment);
|
|
gst_buffer_map (key_buffer, &key_info, GST_MAP_READ);
|
|
|
|
gst_hls_demux_decrypt_start (hlsdemux, key_info.data, hlsdemux->current_iv);
|
|
|
|
gst_buffer_unmap (key_buffer, &key_info);
|
|
gst_buffer_unref (key_buffer);
|
|
g_object_unref (key_fragment);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
key_failed:
|
|
/* TODO Raise this error to the user */
|
|
GST_WARNING_OBJECT (demux, "Failed to decrypt data");
|
|
return FALSE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream, GstBuffer * buffer, gboolean force)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
if (G_UNLIKELY (hlsdemux->do_typefind && buffer != NULL)) {
|
|
GstCaps *caps = NULL;
|
|
GstMapInfo info;
|
|
guint buffer_size;
|
|
GstTypeFindProbability prob = GST_TYPE_FIND_NONE;
|
|
|
|
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)) {
|
|
caps =
|
|
gst_type_find_helper_for_data (GST_OBJECT_CAST (hlsdemux), info.data,
|
|
info.size, &prob);
|
|
}
|
|
gst_buffer_unmap (buffer, &info);
|
|
|
|
if (G_UNLIKELY (!caps)) {
|
|
/* 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) || force) {
|
|
GST_ELEMENT_ERROR (hlsdemux, STREAM, TYPE_NOT_FOUND,
|
|
("Could not determine type of stream"), (NULL));
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
} else {
|
|
if (hlsdemux->pending_buffer)
|
|
hlsdemux->pending_buffer =
|
|
gst_buffer_append (buffer, hlsdemux->pending_buffer);
|
|
else
|
|
hlsdemux->pending_buffer = buffer;
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (hlsdemux, "Typefind result: %" GST_PTR_FORMAT " prob:%d",
|
|
caps, prob);
|
|
|
|
gst_adaptive_demux_stream_set_caps (stream, caps);
|
|
hlsdemux->do_typefind = FALSE;
|
|
}
|
|
|
|
if (buffer)
|
|
return gst_adaptive_demux_stream_push_buffer (stream, buffer);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_hls_demux_finish_fragment (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
if (hlsdemux->current_key)
|
|
gst_hls_demux_decrypt_end (hlsdemux);
|
|
|
|
/* ideally this should be empty, but this eos might have been
|
|
* caused by an error on the source element */
|
|
GST_DEBUG_OBJECT (demux, "Data still on the adapter when EOS was received"
|
|
": %" G_GSIZE_FORMAT, gst_adapter_available (stream->adapter));
|
|
gst_adapter_clear (stream->adapter);
|
|
|
|
if (stream->last_ret == GST_FLOW_OK) {
|
|
if (hlsdemux->pending_buffer) {
|
|
if (hlsdemux->current_key) {
|
|
GstMapInfo info;
|
|
gssize unpadded_size;
|
|
|
|
/* Handle pkcs7 unpadding here */
|
|
gst_buffer_map (hlsdemux->pending_buffer, &info, GST_MAP_READ);
|
|
unpadded_size = info.size - info.data[info.size - 1];
|
|
gst_buffer_unmap (hlsdemux->pending_buffer, &info);
|
|
|
|
gst_buffer_resize (hlsdemux->pending_buffer, 0, unpadded_size);
|
|
}
|
|
|
|
ret =
|
|
gst_hls_demux_handle_buffer (demux, stream, hlsdemux->pending_buffer,
|
|
TRUE);
|
|
hlsdemux->pending_buffer = NULL;
|
|
}
|
|
} else {
|
|
if (hlsdemux->pending_buffer)
|
|
gst_buffer_unref (hlsdemux->pending_buffer);
|
|
hlsdemux->pending_buffer = NULL;
|
|
}
|
|
|
|
if (ret == GST_FLOW_OK || ret == GST_FLOW_NOT_LINKED)
|
|
return gst_adaptive_demux_stream_advance_fragment (demux, stream,
|
|
stream->fragment.duration);
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_hls_demux_data_received (GstAdaptiveDemux * demux,
|
|
GstAdaptiveDemuxStream * stream)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
gsize available;
|
|
GstBuffer *buffer = NULL;
|
|
|
|
available = gst_adapter_available (stream->adapter);
|
|
|
|
/* Is it encrypted? */
|
|
if (hlsdemux->current_key) {
|
|
GError *err = NULL;
|
|
GstBuffer *tmp_buffer;
|
|
|
|
/* must be a multiple of 16 */
|
|
available = available & (~0xF);
|
|
|
|
if (available == 0) {
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
buffer = gst_adapter_take_buffer (stream->adapter, available);
|
|
buffer = gst_hls_demux_decrypt_fragment (hlsdemux, buffer, &err);
|
|
if (buffer == NULL) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Failed to decrypt buffer"),
|
|
("decryption failed %s", err->message));
|
|
g_error_free (err);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
tmp_buffer = hlsdemux->pending_buffer;
|
|
hlsdemux->pending_buffer = buffer;
|
|
buffer = tmp_buffer;
|
|
} else {
|
|
buffer = gst_adapter_take_buffer (stream->adapter, available);
|
|
if (hlsdemux->pending_buffer) {
|
|
buffer = gst_buffer_append (hlsdemux->pending_buffer, buffer);
|
|
hlsdemux->pending_buffer = NULL;
|
|
}
|
|
}
|
|
|
|
return gst_hls_demux_handle_buffer (demux, stream, buffer, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_stream_has_next_fragment (GstAdaptiveDemuxStream * stream)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
|
|
|
|
return gst_m3u8_client_has_next_fragment (hlsdemux->client,
|
|
stream->demux->segment.rate > 0);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_hls_demux_advance_fragment (GstAdaptiveDemuxStream * stream)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
|
|
|
|
gst_m3u8_client_advance_fragment (hlsdemux->client,
|
|
stream->demux->segment.rate > 0);
|
|
hlsdemux->reset_pts = FALSE;
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_hls_demux_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
|
|
gchar *next_fragment_uri;
|
|
GstClockTime duration;
|
|
GstClockTime timestamp;
|
|
gboolean discont;
|
|
gint64 range_start, range_end;
|
|
gchar *key = NULL;
|
|
guint8 *iv = NULL;
|
|
|
|
if (!gst_m3u8_client_get_next_fragment (hlsdemux->client, &discont,
|
|
&next_fragment_uri, &duration, ×tamp, &range_start, &range_end,
|
|
&key, &iv, stream->demux->segment.rate > 0)) {
|
|
GST_INFO_OBJECT (hlsdemux, "This playlist doesn't contain more fragments");
|
|
return GST_FLOW_EOS;
|
|
}
|
|
|
|
/* set up our source for download */
|
|
if (hlsdemux->reset_pts || discont) {
|
|
stream->fragment.timestamp = timestamp;
|
|
} else {
|
|
stream->fragment.timestamp = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
if (hlsdemux->current_key)
|
|
g_free (hlsdemux->current_key);
|
|
hlsdemux->current_key = key;
|
|
if (hlsdemux->current_iv)
|
|
g_free (hlsdemux->current_iv);
|
|
hlsdemux->current_iv = iv;
|
|
g_free (stream->fragment.uri);
|
|
stream->fragment.uri = next_fragment_uri;
|
|
stream->fragment.range_start = range_start;
|
|
stream->fragment.range_end = range_end;
|
|
if (discont)
|
|
stream->discont = discont;
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, guint64 bitrate)
|
|
{
|
|
GstAdaptiveDemux *demux = GST_ADAPTIVE_DEMUX_CAST (stream->demux);
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (stream->demux);
|
|
gboolean changed = FALSE;
|
|
|
|
GST_M3U8_CLIENT_LOCK (hlsdemux->client);
|
|
if (!hlsdemux->client->main->lists) {
|
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
|
return FALSE;
|
|
}
|
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
|
|
|
/* FIXME: Currently several issues have be found when letting bitrate adaptation
|
|
* happen using trick modes (such as 'All streams finished without buffers') and
|
|
* the adaptive algorithm does not properly behave. */
|
|
if (demux->segment.rate != 1.0)
|
|
return FALSE;
|
|
|
|
gst_hls_demux_change_playlist (hlsdemux, bitrate, &changed);
|
|
if (changed)
|
|
gst_hls_demux_setup_streams (GST_ADAPTIVE_DEMUX_CAST (hlsdemux));
|
|
return changed;
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_reset (GstAdaptiveDemux * ademux)
|
|
{
|
|
GstHLSDemux *demux = GST_HLS_DEMUX_CAST (ademux);
|
|
|
|
demux->do_typefind = TRUE;
|
|
demux->reset_pts = TRUE;
|
|
|
|
g_free (demux->key_url);
|
|
demux->key_url = NULL;
|
|
|
|
if (demux->key_fragment)
|
|
g_object_unref (demux->key_fragment);
|
|
demux->key_fragment = NULL;
|
|
|
|
if (demux->client) {
|
|
gst_m3u8_client_free (demux->client);
|
|
demux->client = NULL;
|
|
}
|
|
/* TODO recreated on hls only if reset was not for disposing */
|
|
demux->client = gst_m3u8_client_new ("", NULL);
|
|
|
|
demux->srcpad_counter = 0;
|
|
if (demux->pending_buffer)
|
|
gst_buffer_unref (demux->pending_buffer);
|
|
demux->pending_buffer = NULL;
|
|
if (demux->current_key) {
|
|
g_free (demux->current_key);
|
|
demux->current_key = NULL;
|
|
}
|
|
if (demux->current_iv) {
|
|
g_free (demux->current_iv);
|
|
demux->current_iv = NULL;
|
|
}
|
|
|
|
gst_hls_demux_decrypt_end (demux);
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_set_location (GstHLSDemux * demux, const gchar * uri,
|
|
const gchar * base_uri)
|
|
{
|
|
if (demux->client)
|
|
gst_m3u8_client_free (demux->client);
|
|
demux->client = gst_m3u8_client_new (uri, base_uri);
|
|
GST_INFO_OBJECT (demux, "Changed location: %s (base uri: %s)", uri,
|
|
GST_STR_NULL (base_uri));
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
gst_hls_src_buf_to_utf8_playlist (GstBuffer * buf)
|
|
{
|
|
GstMapInfo info;
|
|
gchar *playlist;
|
|
|
|
if (!gst_buffer_map (buf, &info, GST_MAP_READ))
|
|
goto map_error;
|
|
|
|
if (!g_utf8_validate ((gchar *) info.data, info.size, NULL))
|
|
goto validate_error;
|
|
|
|
/* alloc size + 1 to end with a null character */
|
|
playlist = g_malloc0 (info.size + 1);
|
|
memcpy (playlist, info.data, info.size);
|
|
|
|
gst_buffer_unmap (buf, &info);
|
|
return playlist;
|
|
|
|
validate_error:
|
|
gst_buffer_unmap (buf, &info);
|
|
map_error:
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update,
|
|
GError ** err)
|
|
{
|
|
GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX (demux);
|
|
GstFragment *download;
|
|
GstBuffer *buf;
|
|
gchar *playlist;
|
|
gboolean main_checked = FALSE, updated = FALSE;
|
|
gchar *uri, *main_uri;
|
|
|
|
retry:
|
|
uri = gst_m3u8_client_get_current_uri (demux->client);
|
|
main_uri = gst_m3u8_client_get_uri (demux->client);
|
|
download =
|
|
gst_uri_downloader_fetch_uri (adaptive_demux->downloader, uri, main_uri,
|
|
TRUE, TRUE, TRUE, err);
|
|
g_free (main_uri);
|
|
if (download == NULL) {
|
|
if (!adaptive_demux->cancelled && update && !main_checked
|
|
&& gst_m3u8_client_has_variant_playlist (demux->client)
|
|
&& gst_m3u8_client_has_main (demux->client)) {
|
|
GError *err2 = NULL;
|
|
main_uri = gst_m3u8_client_get_uri (demux->client);
|
|
GST_INFO_OBJECT (demux,
|
|
"Updating playlist %s failed, attempt to refresh variant playlist %s",
|
|
uri, main_uri);
|
|
download =
|
|
gst_uri_downloader_fetch_uri (adaptive_demux->downloader,
|
|
main_uri, NULL, TRUE, TRUE, TRUE, &err2);
|
|
g_free (main_uri);
|
|
g_clear_error (&err2);
|
|
if (download != NULL) {
|
|
gchar *base_uri;
|
|
|
|
buf = gst_fragment_get_buffer (download);
|
|
playlist = gst_hls_src_buf_to_utf8_playlist (buf);
|
|
gst_buffer_unref (buf);
|
|
|
|
if (playlist == NULL) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Failed to validate variant playlist encoding");
|
|
g_free (uri);
|
|
g_object_unref (download);
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (uri);
|
|
if (download->redirect_permanent && download->redirect_uri) {
|
|
uri = download->redirect_uri;
|
|
base_uri = NULL;
|
|
} else {
|
|
uri = download->uri;
|
|
base_uri = download->redirect_uri;
|
|
}
|
|
|
|
if (!gst_m3u8_client_update_variant_playlist (demux->client, playlist,
|
|
uri, base_uri)) {
|
|
GST_WARNING_OBJECT (demux, "Failed to update the variant playlist");
|
|
g_object_unref (download);
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_unref (download);
|
|
|
|
g_clear_error (err);
|
|
main_checked = TRUE;
|
|
goto retry;
|
|
} else {
|
|
g_free (uri);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
g_free (uri);
|
|
return FALSE;
|
|
}
|
|
}
|
|
g_free (uri);
|
|
|
|
/* Set the base URI of the playlist to the redirect target if any */
|
|
GST_M3U8_CLIENT_LOCK (demux->client);
|
|
g_free (demux->client->current->uri);
|
|
g_free (demux->client->current->base_uri);
|
|
if (download->redirect_permanent && download->redirect_uri) {
|
|
demux->client->current->uri = g_strdup (download->redirect_uri);
|
|
demux->client->current->base_uri = NULL;
|
|
} else {
|
|
demux->client->current->uri = g_strdup (download->uri);
|
|
demux->client->current->base_uri = g_strdup (download->redirect_uri);
|
|
}
|
|
GST_M3U8_CLIENT_UNLOCK (demux->client);
|
|
|
|
buf = gst_fragment_get_buffer (download);
|
|
playlist = gst_hls_src_buf_to_utf8_playlist (buf);
|
|
gst_buffer_unref (buf);
|
|
g_object_unref (download);
|
|
|
|
if (playlist == NULL) {
|
|
GST_WARNING_OBJECT (demux, "Couldn't validate playlist encoding");
|
|
g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_WRONG_TYPE,
|
|
"Couldn't validate playlist encoding");
|
|
return FALSE;
|
|
}
|
|
|
|
updated = gst_m3u8_client_update (demux->client, playlist);
|
|
if (!updated) {
|
|
GST_WARNING_OBJECT (demux, "Couldn't update playlist");
|
|
g_set_error (err, GST_STREAM_ERROR, GST_STREAM_ERROR_FAILED,
|
|
"Couldn't update playlist");
|
|
return FALSE;
|
|
}
|
|
|
|
/* If it's a live source, do not let the sequence number go beyond
|
|
* three fragments before the end of the list */
|
|
if (update == FALSE && demux->client->current &&
|
|
gst_m3u8_client_is_live (demux->client)) {
|
|
gint64 last_sequence, first_sequence;
|
|
|
|
GST_M3U8_CLIENT_LOCK (demux->client);
|
|
last_sequence =
|
|
GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->
|
|
files)->data)->sequence;
|
|
first_sequence =
|
|
GST_M3U8_MEDIA_FILE (demux->client->current->files->data)->sequence;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"sequence:%" G_GINT64_FORMAT " , first_sequence:%" G_GINT64_FORMAT
|
|
" , last_sequence:%" G_GINT64_FORMAT, demux->client->sequence,
|
|
first_sequence, last_sequence);
|
|
if (demux->client->sequence >= last_sequence - 3) {
|
|
//demux->need_segment = TRUE;
|
|
/* Make sure we never go below the minimum sequence number */
|
|
demux->client->sequence = MAX (first_sequence, last_sequence - 3);
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Sequence is beyond playlist. Moving back to %" G_GINT64_FORMAT,
|
|
demux->client->sequence);
|
|
}
|
|
GST_M3U8_CLIENT_UNLOCK (demux->client);
|
|
} else if (demux->client->current && !gst_m3u8_client_is_live (demux->client)) {
|
|
GstClockTime current_pos, target_pos;
|
|
guint sequence = 0;
|
|
GList *walk;
|
|
|
|
/* Sequence numbers are not guaranteed to be the same in different
|
|
* playlists, so get the correct fragment here based on the current
|
|
* position
|
|
*/
|
|
GST_M3U8_CLIENT_LOCK (demux->client);
|
|
|
|
/* Valid because hlsdemux only has a single output */
|
|
if (GST_ADAPTIVE_DEMUX_CAST (demux)->streams) {
|
|
GstAdaptiveDemuxStream *stream =
|
|
GST_ADAPTIVE_DEMUX_CAST (demux)->streams->data;
|
|
target_pos = stream->segment.position;
|
|
} else {
|
|
target_pos = 0;
|
|
}
|
|
if (GST_CLOCK_TIME_IS_VALID (demux->client->sequence_position)) {
|
|
target_pos = MAX (target_pos, demux->client->sequence_position);
|
|
}
|
|
|
|
current_pos = 0;
|
|
for (walk = demux->client->current->files; walk; walk = walk->next) {
|
|
GstM3U8MediaFile *file = walk->data;
|
|
|
|
sequence = file->sequence;
|
|
if (current_pos <= target_pos
|
|
&& target_pos < current_pos + file->duration) {
|
|
break;
|
|
}
|
|
current_pos += file->duration;
|
|
}
|
|
/* End of playlist */
|
|
if (!walk)
|
|
sequence++;
|
|
demux->client->sequence = sequence;
|
|
demux->client->sequence_position = current_pos;
|
|
GST_M3U8_CLIENT_UNLOCK (demux->client);
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_change_playlist (GstHLSDemux * demux, guint max_bitrate,
|
|
gboolean * changed)
|
|
{
|
|
GList *previous_variant, *current_variant;
|
|
gint old_bandwidth, new_bandwidth;
|
|
GstAdaptiveDemux *adaptive_demux = GST_ADAPTIVE_DEMUX_CAST (demux);
|
|
GstAdaptiveDemuxStream *stream;
|
|
|
|
g_return_val_if_fail (adaptive_demux->streams != NULL, FALSE);
|
|
|
|
stream = adaptive_demux->streams->data;
|
|
|
|
previous_variant = demux->client->main->current_variant;
|
|
current_variant = gst_m3u8_client_get_playlist_for_bitrate (demux->client,
|
|
max_bitrate);
|
|
|
|
GST_M3U8_CLIENT_LOCK (demux->client);
|
|
|
|
retry_failover_protection:
|
|
old_bandwidth = GST_M3U8 (previous_variant->data)->bandwidth;
|
|
new_bandwidth = GST_M3U8 (current_variant->data)->bandwidth;
|
|
|
|
/* Don't do anything else if the playlist is the same */
|
|
if (new_bandwidth == old_bandwidth) {
|
|
GST_M3U8_CLIENT_UNLOCK (demux->client);
|
|
return TRUE;
|
|
}
|
|
|
|
demux->client->main->current_variant = current_variant;
|
|
GST_M3U8_CLIENT_UNLOCK (demux->client);
|
|
|
|
gst_m3u8_client_set_current (demux->client, current_variant->data);
|
|
|
|
GST_INFO_OBJECT (demux, "Client was on %dbps, max allowed is %dbps, switching"
|
|
" to bitrate %dbps", old_bandwidth, max_bitrate, new_bandwidth);
|
|
stream->discont = TRUE;
|
|
|
|
if (gst_hls_demux_update_playlist (demux, FALSE, NULL)) {
|
|
gchar *uri;
|
|
gchar *main_uri;
|
|
uri = gst_m3u8_client_get_current_uri (demux->client);
|
|
main_uri = gst_m3u8_client_get_uri (demux->client);
|
|
gst_element_post_message (GST_ELEMENT_CAST (demux),
|
|
gst_message_new_element (GST_OBJECT_CAST (demux),
|
|
gst_structure_new (GST_ADAPTIVE_DEMUX_STATISTICS_MESSAGE_NAME,
|
|
"manifest-uri", G_TYPE_STRING,
|
|
main_uri, "uri", G_TYPE_STRING,
|
|
uri, "bitrate", G_TYPE_INT, new_bandwidth, NULL)));
|
|
g_free (uri);
|
|
g_free (main_uri);
|
|
if (changed)
|
|
*changed = TRUE;
|
|
} else {
|
|
GList *failover = NULL;
|
|
|
|
GST_INFO_OBJECT (demux, "Unable to update playlist. Switching back");
|
|
GST_M3U8_CLIENT_LOCK (demux->client);
|
|
|
|
failover = g_list_previous (current_variant);
|
|
if (failover && new_bandwidth == GST_M3U8 (failover->data)->bandwidth) {
|
|
current_variant = failover;
|
|
goto retry_failover_protection;
|
|
}
|
|
|
|
demux->client->main->current_variant = previous_variant;
|
|
GST_M3U8_CLIENT_UNLOCK (demux->client);
|
|
gst_m3u8_client_set_current (demux->client, previous_variant->data);
|
|
/* Try a lower bitrate (or stop if we just tried the lowest) */
|
|
if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
|
|
GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->data)->
|
|
bandwidth)
|
|
return FALSE;
|
|
else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
|
|
GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth)
|
|
return FALSE;
|
|
else
|
|
return gst_hls_demux_change_playlist (demux, new_bandwidth - 1, changed);
|
|
}
|
|
|
|
/* Force typefinding since we might have changed media type */
|
|
demux->do_typefind = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#if defined(HAVE_OPENSSL)
|
|
static gboolean
|
|
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
|
|
const guint8 * iv_data)
|
|
{
|
|
EVP_CIPHER_CTX_init (&demux->aes_ctx);
|
|
if (!EVP_DecryptInit_ex (&demux->aes_ctx, EVP_aes_128_cbc (), NULL, key_data,
|
|
iv_data))
|
|
return FALSE;
|
|
EVP_CIPHER_CTX_set_padding (&demux->aes_ctx, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
decrypt_fragment (GstHLSDemux * demux, gsize length,
|
|
const guint8 * encrypted_data, guint8 * decrypted_data)
|
|
{
|
|
int len, flen = 0;
|
|
|
|
if (G_UNLIKELY (length > G_MAXINT || length % 16 != 0))
|
|
return FALSE;
|
|
|
|
len = (int) length;
|
|
if (!EVP_DecryptUpdate (&demux->aes_ctx, decrypted_data, &len, encrypted_data,
|
|
len))
|
|
return FALSE;
|
|
EVP_DecryptFinal_ex (&demux->aes_ctx, decrypted_data + len, &flen);
|
|
g_return_val_if_fail (len + flen == length, FALSE);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_decrypt_end (GstHLSDemux * demux)
|
|
{
|
|
EVP_CIPHER_CTX_cleanup (&demux->aes_ctx);
|
|
}
|
|
|
|
#elif defined(HAVE_NETTLE)
|
|
static gboolean
|
|
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
|
|
const guint8 * iv_data)
|
|
{
|
|
aes_set_decrypt_key (&demux->aes_ctx.ctx, 16, key_data);
|
|
CBC_SET_IV (&demux->aes_ctx, iv_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
decrypt_fragment (GstHLSDemux * demux, gsize length,
|
|
const guint8 * encrypted_data, guint8 * decrypted_data)
|
|
{
|
|
if (length % 16 != 0)
|
|
return FALSE;
|
|
|
|
CBC_DECRYPT (&demux->aes_ctx, aes_decrypt, length, decrypted_data,
|
|
encrypted_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_decrypt_end (GstHLSDemux * demux)
|
|
{
|
|
/* NOP */
|
|
}
|
|
|
|
#else
|
|
static gboolean
|
|
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
|
|
const guint8 * iv_data)
|
|
{
|
|
gcry_error_t err = 0;
|
|
gboolean ret = FALSE;
|
|
|
|
err =
|
|
gcry_cipher_open (&demux->aes_ctx, GCRY_CIPHER_AES128,
|
|
GCRY_CIPHER_MODE_CBC, 0);
|
|
if (err)
|
|
goto out;
|
|
err = gcry_cipher_setkey (demux->aes_ctx, key_data, 16);
|
|
if (err)
|
|
goto out;
|
|
err = gcry_cipher_setiv (demux->aes_ctx, iv_data, 16);
|
|
if (!err)
|
|
ret = TRUE;
|
|
|
|
out:
|
|
if (!ret)
|
|
if (demux->aes_ctx)
|
|
gcry_cipher_close (demux->aes_ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
decrypt_fragment (GstHLSDemux * demux, gsize length,
|
|
const guint8 * encrypted_data, guint8 * decrypted_data)
|
|
{
|
|
gcry_error_t err = 0;
|
|
|
|
err = gcry_cipher_decrypt (demux->aes_ctx, decrypted_data, length,
|
|
encrypted_data, length);
|
|
|
|
return err == 0;
|
|
}
|
|
|
|
static void
|
|
gst_hls_demux_decrypt_end (GstHLSDemux * demux)
|
|
{
|
|
if (demux->aes_ctx) {
|
|
gcry_cipher_close (demux->aes_ctx);
|
|
demux->aes_ctx = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static GstBuffer *
|
|
gst_hls_demux_decrypt_fragment (GstHLSDemux * demux,
|
|
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 (demux, 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 gint64
|
|
gst_hls_demux_get_manifest_update_interval (GstAdaptiveDemux * demux)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
return gst_util_uint64_scale (gst_m3u8_client_get_target_duration
|
|
(hlsdemux->client), G_USEC_PER_SEC, GST_SECOND);
|
|
}
|
|
|
|
static gboolean
|
|
gst_hls_demux_get_live_seek_range (GstAdaptiveDemux * demux, gint64 * start,
|
|
gint64 * stop)
|
|
{
|
|
GstHLSDemux *hlsdemux = GST_HLS_DEMUX_CAST (demux);
|
|
|
|
return gst_m3u8_client_get_seek_range (hlsdemux->client, start, stop);
|
|
}
|