mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 16:21:17 +00:00
b3a7242910
...instead of adding them from the start of playlist every time. This among other things fixes timestamps for live streams, where the playlist is some kind of ringbuffer of fragments and thus adding from the beginning of the playlist will miss the past fragments. https://bugzilla.gnome.org/show_bug.cgi?id=724983
831 lines
20 KiB
C
831 lines
20 KiB
C
/* GStreamer
|
|
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
|
|
*
|
|
* m3u8.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.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <errno.h>
|
|
#include <glib.h>
|
|
#include <string.h>
|
|
|
|
#include "gstfragmented.h"
|
|
#include "m3u8.h"
|
|
|
|
#define GST_CAT_DEFAULT fragmented_debug
|
|
|
|
static GstM3U8 *gst_m3u8_new (void);
|
|
static void gst_m3u8_free (GstM3U8 * m3u8);
|
|
static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
|
|
gboolean * updated);
|
|
static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
|
|
gchar * title, GstClockTime duration, guint sequence);
|
|
static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
|
|
gchar *uri_join (const gchar * uri, const gchar * path);
|
|
|
|
static GstM3U8 *
|
|
gst_m3u8_new (void)
|
|
{
|
|
GstM3U8 *m3u8;
|
|
|
|
m3u8 = g_new0 (GstM3U8, 1);
|
|
|
|
return m3u8;
|
|
}
|
|
|
|
static void
|
|
gst_m3u8_set_uri (GstM3U8 * self, gchar * uri)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
if (self->uri)
|
|
g_free (self->uri);
|
|
self->uri = uri;
|
|
}
|
|
|
|
static void
|
|
gst_m3u8_free (GstM3U8 * self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
g_free (self->uri);
|
|
g_free (self->allowcache);
|
|
g_free (self->codecs);
|
|
g_free (self->key);
|
|
|
|
g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
|
|
g_list_free (self->files);
|
|
|
|
g_free (self->last_data);
|
|
g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
|
|
g_list_free (self->lists);
|
|
|
|
g_free (self);
|
|
}
|
|
|
|
static GstM3U8MediaFile *
|
|
gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
|
|
guint sequence)
|
|
{
|
|
GstM3U8MediaFile *file;
|
|
|
|
file = g_new0 (GstM3U8MediaFile, 1);
|
|
file->uri = uri;
|
|
file->title = title;
|
|
file->duration = duration;
|
|
file->sequence = sequence;
|
|
|
|
return file;
|
|
}
|
|
|
|
static void
|
|
gst_m3u8_media_file_free (GstM3U8MediaFile * self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
g_free (self->title);
|
|
g_free (self->uri);
|
|
g_free (self->key);
|
|
g_free (self);
|
|
}
|
|
|
|
static gboolean
|
|
int_from_string (gchar * ptr, gchar ** endptr, gint * val)
|
|
{
|
|
gchar *end;
|
|
gint64 ret;
|
|
|
|
g_return_val_if_fail (ptr != NULL, FALSE);
|
|
g_return_val_if_fail (val != NULL, FALSE);
|
|
|
|
errno = 0;
|
|
ret = g_ascii_strtoll (ptr, &end, 10);
|
|
if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
|
|
|| (errno != 0 && ret == 0)) {
|
|
GST_WARNING ("%s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (ret > G_MAXINT || ret < G_MININT) {
|
|
GST_WARNING ("%s", g_strerror (ERANGE));
|
|
return FALSE;
|
|
}
|
|
|
|
if (endptr)
|
|
*endptr = end;
|
|
|
|
*val = (gint) ret;
|
|
|
|
return end != ptr;
|
|
}
|
|
|
|
static gboolean
|
|
double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
|
|
{
|
|
gchar *end;
|
|
gdouble ret;
|
|
|
|
g_return_val_if_fail (ptr != NULL, FALSE);
|
|
g_return_val_if_fail (val != NULL, FALSE);
|
|
|
|
errno = 0;
|
|
ret = g_ascii_strtod (ptr, &end);
|
|
if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
|
|
|| (errno != 0 && ret == 0)) {
|
|
GST_WARNING ("%s", g_strerror (errno));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!isfinite (ret)) {
|
|
GST_WARNING ("%s", g_strerror (ERANGE));
|
|
return FALSE;
|
|
}
|
|
|
|
if (endptr)
|
|
*endptr = end;
|
|
|
|
*val = (gdouble) ret;
|
|
|
|
return end != ptr;
|
|
}
|
|
|
|
static gboolean
|
|
parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
|
|
{
|
|
gchar *end, *p;
|
|
|
|
g_return_val_if_fail (ptr != NULL, FALSE);
|
|
g_return_val_if_fail (*ptr != NULL, FALSE);
|
|
g_return_val_if_fail (a != NULL, FALSE);
|
|
g_return_val_if_fail (v != NULL, FALSE);
|
|
|
|
/* [attribute=value,]* */
|
|
|
|
*a = *ptr;
|
|
end = p = g_utf8_strchr (*ptr, -1, ',');
|
|
if (end) {
|
|
do {
|
|
end = g_utf8_next_char (end);
|
|
} while (end && *end == ' ');
|
|
*p = '\0';
|
|
}
|
|
|
|
*v = p = g_utf8_strchr (*ptr, -1, '=');
|
|
if (*v) {
|
|
*v = g_utf8_next_char (*v);
|
|
*p = '\0';
|
|
} else {
|
|
GST_WARNING ("missing = after attribute");
|
|
return FALSE;
|
|
}
|
|
|
|
*ptr = end;
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
_m3u8_compare_uri (GstM3U8 * a, gchar * uri)
|
|
{
|
|
g_return_val_if_fail (a != NULL, 0);
|
|
g_return_val_if_fail (uri != NULL, 0);
|
|
|
|
return g_strcmp0 (a->uri, uri);
|
|
}
|
|
|
|
static gint
|
|
gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
|
|
{
|
|
return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
|
|
}
|
|
|
|
/*
|
|
* @data: a m3u8 playlist text data, taking ownership
|
|
*/
|
|
static gboolean
|
|
gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
|
|
{
|
|
gint val;
|
|
GstClockTime duration;
|
|
gchar *title, *end;
|
|
// gboolean discontinuity;
|
|
GstM3U8 *list;
|
|
gboolean have_iv = FALSE;
|
|
guint8 iv[16] = { 0, };
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
g_return_val_if_fail (data != NULL, FALSE);
|
|
g_return_val_if_fail (updated != NULL, FALSE);
|
|
|
|
*updated = TRUE;
|
|
|
|
/* check if the data changed since last update */
|
|
if (self->last_data && g_str_equal (self->last_data, data)) {
|
|
GST_DEBUG ("Playlist is the same as previous one");
|
|
*updated = FALSE;
|
|
g_free (data);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!g_str_has_prefix (data, "#EXTM3U")) {
|
|
GST_WARNING ("Data doesn't start with #EXTM3U");
|
|
*updated = FALSE;
|
|
g_free (data);
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (self->last_data);
|
|
self->last_data = data;
|
|
|
|
if (self->files) {
|
|
g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
|
|
g_list_free (self->files);
|
|
self->files = NULL;
|
|
}
|
|
|
|
list = NULL;
|
|
duration = 0;
|
|
title = NULL;
|
|
data += 7;
|
|
while (TRUE) {
|
|
gchar *r;
|
|
|
|
end = g_utf8_strchr (data, -1, '\n');
|
|
if (end)
|
|
*end = '\0';
|
|
|
|
r = g_utf8_strchr (data, -1, '\r');
|
|
if (r)
|
|
*r = '\0';
|
|
|
|
if (data[0] != '#' && data[0] != '\0') {
|
|
if (duration <= 0 && list == NULL) {
|
|
GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
|
|
goto next_line;
|
|
}
|
|
|
|
data = uri_join (self->uri, data);
|
|
if (data == NULL)
|
|
goto next_line;
|
|
|
|
if (list != NULL) {
|
|
if (g_list_find_custom (self->lists, data,
|
|
(GCompareFunc) _m3u8_compare_uri)) {
|
|
GST_DEBUG ("Already have a list with this URI");
|
|
gst_m3u8_free (list);
|
|
g_free (data);
|
|
} else {
|
|
gst_m3u8_set_uri (list, data);
|
|
self->lists = g_list_append (self->lists, list);
|
|
}
|
|
list = NULL;
|
|
} else {
|
|
GstM3U8MediaFile *file;
|
|
file =
|
|
gst_m3u8_media_file_new (data, title, duration,
|
|
self->mediasequence++);
|
|
|
|
/* set encryption params */
|
|
file->key = g_strdup (self->key);
|
|
if (file->key) {
|
|
if (have_iv) {
|
|
memcpy (file->iv, iv, sizeof (iv));
|
|
} else {
|
|
guint8 *iv = file->iv + 12;
|
|
GST_WRITE_UINT32_BE (iv + 12, file->sequence);
|
|
}
|
|
}
|
|
|
|
duration = 0;
|
|
title = NULL;
|
|
self->files = g_list_append (self->files, file);
|
|
}
|
|
|
|
} else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
|
|
self->endlist = TRUE;
|
|
} else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
|
|
if (int_from_string (data + 15, &data, &val))
|
|
self->version = val;
|
|
} else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) {
|
|
gchar *v, *a;
|
|
|
|
if (list != NULL) {
|
|
GST_WARNING ("Found a list without a uri..., dropping");
|
|
gst_m3u8_free (list);
|
|
}
|
|
|
|
list = gst_m3u8_new ();
|
|
data = data + 18;
|
|
while (data && parse_attributes (&data, &a, &v)) {
|
|
if (g_str_equal (a, "BANDWIDTH")) {
|
|
if (!int_from_string (v, NULL, &list->bandwidth))
|
|
GST_WARNING ("Error while reading BANDWIDTH");
|
|
} else if (g_str_equal (a, "PROGRAM-ID")) {
|
|
if (!int_from_string (v, NULL, &list->program_id))
|
|
GST_WARNING ("Error while reading PROGRAM-ID");
|
|
} else if (g_str_equal (a, "CODECS")) {
|
|
g_free (list->codecs);
|
|
list->codecs = g_strdup (v);
|
|
} else if (g_str_equal (a, "RESOLUTION")) {
|
|
if (!int_from_string (v, &v, &list->width))
|
|
GST_WARNING ("Error while reading RESOLUTION width");
|
|
if (!v || *v != '=') {
|
|
GST_WARNING ("Missing height");
|
|
} else {
|
|
v = g_utf8_next_char (v);
|
|
if (!int_from_string (v, NULL, &list->height))
|
|
GST_WARNING ("Error while reading RESOLUTION height");
|
|
}
|
|
}
|
|
}
|
|
} else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
|
|
if (int_from_string (data + 22, &data, &val))
|
|
self->targetduration = val * GST_SECOND;
|
|
} else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
|
|
if (int_from_string (data + 22, &data, &val))
|
|
self->mediasequence = val;
|
|
} else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
|
|
/* discontinuity = TRUE; */
|
|
} else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
|
|
/* <YYYY-MM-DDThh:mm:ssZ> */
|
|
GST_DEBUG ("FIXME parse date");
|
|
} else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
|
|
g_free (self->allowcache);
|
|
self->allowcache = g_strdup (data + 19);
|
|
} else if (g_str_has_prefix (data, "#EXT-X-KEY:")) {
|
|
gchar *v, *a;
|
|
|
|
data = data + 11;
|
|
|
|
/* IV and KEY are only valid until the next #EXT-X-KEY */
|
|
have_iv = FALSE;
|
|
g_free (self->key);
|
|
self->key = NULL;
|
|
while (data && parse_attributes (&data, &a, &v)) {
|
|
if (g_str_equal (a, "URI")) {
|
|
gchar *key = g_strdup (v);
|
|
gchar *keyp = key;
|
|
int len = strlen (key);
|
|
|
|
/* handle the \"key\" case */
|
|
if (key[len - 1] == '"')
|
|
key[len - 1] = '\0';
|
|
if (key[0] == '"')
|
|
key += 1;
|
|
|
|
self->key = uri_join (self->uri, key);
|
|
g_free (keyp);
|
|
} else if (g_str_equal (a, "IV")) {
|
|
gchar *ivp = v;
|
|
gint i;
|
|
|
|
if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
|
|
&& !g_str_has_prefix (ivp, "0X"))) {
|
|
GST_WARNING ("Can't read IV");
|
|
continue;
|
|
}
|
|
|
|
ivp += 2;
|
|
for (i = 0; i < 16; i++) {
|
|
gint h, l;
|
|
|
|
h = g_ascii_xdigit_value (*ivp);
|
|
ivp++;
|
|
l = g_ascii_xdigit_value (*ivp);
|
|
ivp++;
|
|
if (h == -1 || l == -1) {
|
|
i = -1;
|
|
break;
|
|
}
|
|
iv[i] = (h << 4) | l;
|
|
}
|
|
|
|
if (i == -1) {
|
|
GST_WARNING ("Can't read IV");
|
|
continue;
|
|
}
|
|
have_iv = TRUE;
|
|
} else if (g_str_equal (a, "METHOD")) {
|
|
if (!g_str_equal (v, "AES-128")) {
|
|
GST_WARNING ("Encryption method %s not supported", v);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
} else if (g_str_has_prefix (data, "#EXTINF:")) {
|
|
gdouble fval;
|
|
if (!double_from_string (data + 8, &data, &fval)) {
|
|
GST_WARNING ("Can't read EXTINF duration");
|
|
goto next_line;
|
|
}
|
|
duration = fval * (gdouble) GST_SECOND;
|
|
if (duration > self->targetduration)
|
|
GST_WARNING ("EXTINF duration > TARGETDURATION");
|
|
if (!data || *data != ',')
|
|
goto next_line;
|
|
data = g_utf8_next_char (data);
|
|
if (data != end) {
|
|
g_free (title);
|
|
title = g_strdup (data);
|
|
}
|
|
} else {
|
|
GST_LOG ("Ignored line: %s", data);
|
|
}
|
|
|
|
next_line:
|
|
if (!end)
|
|
break;
|
|
data = g_utf8_next_char (end); /* skip \n */
|
|
}
|
|
|
|
/* redorder playlists by bitrate */
|
|
if (self->lists) {
|
|
gchar *top_variant_uri = NULL;
|
|
|
|
if (!self->current_variant)
|
|
top_variant_uri = GST_M3U8 (self->lists->data)->uri;
|
|
else
|
|
top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
|
|
|
|
self->lists =
|
|
g_list_sort (self->lists,
|
|
(GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
|
|
|
|
self->current_variant = g_list_find_custom (self->lists, top_variant_uri,
|
|
(GCompareFunc) _m3u8_compare_uri);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GstM3U8Client *
|
|
gst_m3u8_client_new (const gchar * uri)
|
|
{
|
|
GstM3U8Client *client;
|
|
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
client = g_new0 (GstM3U8Client, 1);
|
|
client->main = gst_m3u8_new ();
|
|
client->current = NULL;
|
|
client->sequence = -1;
|
|
client->sequence_position = 0;
|
|
client->update_failed_count = 0;
|
|
g_mutex_init (&client->lock);
|
|
gst_m3u8_set_uri (client->main, g_strdup (uri));
|
|
|
|
return client;
|
|
}
|
|
|
|
void
|
|
gst_m3u8_client_free (GstM3U8Client * self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
gst_m3u8_free (self->main);
|
|
g_mutex_clear (&self->lock);
|
|
g_free (self);
|
|
}
|
|
|
|
void
|
|
gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
GST_M3U8_CLIENT_LOCK (self);
|
|
if (m3u8 != self->current) {
|
|
self->current = m3u8;
|
|
self->update_failed_count = 0;
|
|
}
|
|
GST_M3U8_CLIENT_UNLOCK (self);
|
|
}
|
|
|
|
gboolean
|
|
gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
|
|
{
|
|
GstM3U8 *m3u8;
|
|
gboolean updated = FALSE;
|
|
gboolean ret = FALSE;
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
|
|
GST_M3U8_CLIENT_LOCK (self);
|
|
m3u8 = self->current ? self->current : self->main;
|
|
|
|
if (!gst_m3u8_update (m3u8, data, &updated))
|
|
goto out;
|
|
|
|
if (!updated) {
|
|
self->update_failed_count++;
|
|
goto out;
|
|
}
|
|
|
|
/* select the first playlist, for now */
|
|
if (!self->current) {
|
|
if (self->main->lists) {
|
|
self->current = self->main->current_variant->data;
|
|
} else {
|
|
self->current = self->main;
|
|
}
|
|
}
|
|
|
|
if (m3u8->files && self->sequence == -1) {
|
|
self->sequence =
|
|
GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
|
|
self->sequence_position = 0;
|
|
GST_DEBUG ("Setting first sequence at %d", self->sequence);
|
|
}
|
|
|
|
ret = TRUE;
|
|
out:
|
|
GST_M3U8_CLIENT_UNLOCK (self);
|
|
return ret;
|
|
}
|
|
|
|
static gint
|
|
_find_current (GstM3U8MediaFile * file, GstM3U8Client * client)
|
|
{
|
|
return file->sequence == client->sequence;
|
|
}
|
|
|
|
static gboolean
|
|
_find_next (GstM3U8MediaFile * file, GstM3U8Client * client)
|
|
{
|
|
GST_DEBUG ("Found fragment %d", file->sequence);
|
|
if (file->sequence >= client->sequence)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
|
|
gboolean * discontinuity, const gchar ** uri, GstClockTime * duration,
|
|
GstClockTime * timestamp, const gchar ** key, const guint8 ** iv)
|
|
{
|
|
GList *l;
|
|
GstM3U8MediaFile *file;
|
|
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
g_return_val_if_fail (client->current != NULL, FALSE);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
GST_DEBUG ("Looking for fragment %d", client->sequence);
|
|
l = g_list_find_custom (client->current->files, client,
|
|
(GCompareFunc) _find_next);
|
|
if (l == NULL) {
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return FALSE;
|
|
}
|
|
|
|
file = GST_M3U8_MEDIA_FILE (l->data);
|
|
GST_DEBUG ("Got fragment with sequence %u (client sequence %u)",
|
|
file->sequence, client->sequence);
|
|
|
|
if (timestamp)
|
|
*timestamp = client->sequence_position;
|
|
|
|
if (discontinuity)
|
|
*discontinuity = client->sequence != file->sequence;
|
|
if (uri)
|
|
*uri = file->uri;
|
|
if (duration)
|
|
*duration = file->duration;
|
|
if (key)
|
|
*key = file->key;
|
|
if (iv)
|
|
*iv = file->iv;
|
|
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gst_m3u8_client_advance_fragment (GstM3U8Client * client)
|
|
{
|
|
GList *l;
|
|
GstM3U8MediaFile *file;
|
|
|
|
g_return_if_fail (client != NULL);
|
|
g_return_if_fail (client->current != NULL);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
GST_DEBUG ("Looking for fragment %d", client->sequence);
|
|
l = g_list_find_custom (client->current->files, client,
|
|
(GCompareFunc) _find_next);
|
|
if (l == NULL) {
|
|
GST_ERROR ("Could not find current fragment");
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return;
|
|
}
|
|
|
|
file = GST_M3U8_MEDIA_FILE (l->data);
|
|
GST_DEBUG ("Advancing from sequence %u", file->sequence);
|
|
client->sequence = file->sequence + 1;
|
|
client->sequence_position += file->duration;
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
}
|
|
|
|
static void
|
|
_sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
|
|
{
|
|
*duration += self->duration;
|
|
}
|
|
|
|
GstClockTime
|
|
gst_m3u8_client_get_duration (GstM3U8Client * client)
|
|
{
|
|
GstClockTime duration = 0;
|
|
|
|
g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
/* We can only get the duration for on-demand streams */
|
|
if (!client->current->endlist) {
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return duration;
|
|
}
|
|
|
|
GstClockTime
|
|
gst_m3u8_client_get_target_duration (GstM3U8Client * client)
|
|
{
|
|
GstClockTime duration = 0;
|
|
|
|
g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
duration = client->current->targetduration;
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return duration;
|
|
}
|
|
|
|
const gchar *
|
|
gst_m3u8_client_get_uri (GstM3U8Client * client)
|
|
{
|
|
const gchar *uri;
|
|
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
uri = client->main->uri;
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return uri;
|
|
}
|
|
|
|
const gchar *
|
|
gst_m3u8_client_get_current_uri (GstM3U8Client * client)
|
|
{
|
|
const gchar *uri;
|
|
|
|
g_return_val_if_fail (client != NULL, NULL);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
uri = client->current->uri;
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return uri;
|
|
}
|
|
|
|
gboolean
|
|
gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
|
|
{
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
ret = (client->main->lists != NULL);
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_m3u8_client_is_live (GstM3U8Client * client)
|
|
{
|
|
gboolean ret;
|
|
|
|
g_return_val_if_fail (client != NULL, FALSE);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
if (!client->current || client->current->endlist)
|
|
ret = FALSE;
|
|
else
|
|
ret = TRUE;
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return ret;
|
|
}
|
|
|
|
GList *
|
|
gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate)
|
|
{
|
|
GList *list, *current_variant;
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
current_variant = client->main->current_variant;
|
|
|
|
/* Go to the highest possible bandwidth allowed */
|
|
while (GST_M3U8 (current_variant->data)->bandwidth < bitrate) {
|
|
list = g_list_next (current_variant);
|
|
if (!list)
|
|
break;
|
|
current_variant = list;
|
|
}
|
|
|
|
while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) {
|
|
list = g_list_previous (current_variant);
|
|
if (!list)
|
|
break;
|
|
current_variant = list;
|
|
}
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
|
|
return current_variant;
|
|
}
|
|
|
|
gchar *
|
|
uri_join (const gchar * uri1, const gchar * uri2)
|
|
{
|
|
gchar *uri_copy, *tmp, *ret = NULL;
|
|
|
|
if (gst_uri_is_valid (uri2))
|
|
return g_strdup (uri2);
|
|
|
|
uri_copy = g_strdup (uri1);
|
|
if (uri2[0] != '/') {
|
|
/* uri2 is a relative uri2 */
|
|
tmp = g_utf8_strrchr (uri_copy, -1, '/');
|
|
if (!tmp) {
|
|
GST_WARNING ("Can't build a valid uri_copy");
|
|
goto out;
|
|
}
|
|
|
|
*tmp = '\0';
|
|
ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
|
|
} else {
|
|
/* uri2 is an absolute uri2 */
|
|
char *scheme, *hostname;
|
|
|
|
scheme = uri_copy;
|
|
/* find the : in <scheme>:// */
|
|
tmp = g_utf8_strchr (uri_copy, -1, ':');
|
|
if (!tmp) {
|
|
GST_WARNING ("Can't build a valid uri_copy");
|
|
goto out;
|
|
}
|
|
|
|
*tmp = '\0';
|
|
|
|
/* skip :// */
|
|
hostname = tmp + 3;
|
|
|
|
tmp = g_utf8_strchr (hostname, -1, '/');
|
|
if (tmp)
|
|
*tmp = '\0';
|
|
|
|
ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
|
|
}
|
|
|
|
out:
|
|
g_free (uri_copy);
|
|
return ret;
|
|
}
|
|
|
|
guint64
|
|
gst_m3u8_client_get_current_fragment_duration (GstM3U8Client * client)
|
|
{
|
|
guint64 dur;
|
|
GList *list;
|
|
|
|
g_return_val_if_fail (client != NULL, 0);
|
|
|
|
GST_M3U8_CLIENT_LOCK (client);
|
|
|
|
list = g_list_find_custom (client->current->files, client,
|
|
(GCompareFunc) _find_current);
|
|
if (list == NULL) {
|
|
dur = -1;
|
|
} else {
|
|
dur = GST_M3U8_MEDIA_FILE (list->data)->duration;
|
|
}
|
|
|
|
GST_M3U8_CLIENT_UNLOCK (client);
|
|
return dur;
|
|
}
|