2
* Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
6
* This library is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU Library General Public
8
* License as published by the Free Software Foundation; either
9
* version 2 of the License, or (at your option) any later version.
11
* This library is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
* Library General Public License for more details.
16
* You should have received a copy of the GNU Library General Public
17
* License along with this library; if not, write to the
18
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19
* Boston, MA 02111-1307, USA.
26
#include "gstfragmented.h"
29
#define GST_CAT_DEFAULT fragmented_debug
31
static GstM3U8 *gst_m3u8_new (void);
32
static void gst_m3u8_free (GstM3U8 * m3u8);
33
static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
35
static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
36
gchar * title, gint duration, guint sequence);
37
static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
44
m3u8 = g_new0 (GstM3U8, 1);
50
gst_m3u8_set_uri (GstM3U8 * self, gchar * uri)
52
g_return_if_fail (self != NULL);
60
gst_m3u8_free (GstM3U8 * self)
62
g_return_if_fail (self != NULL);
65
g_free (self->allowcache);
66
g_free (self->codecs);
68
g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
69
g_list_free (self->files);
71
g_free (self->last_data);
72
g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
73
g_list_free (self->lists);
78
static GstM3U8MediaFile *
79
gst_m3u8_media_file_new (gchar * uri, gchar * title, gint duration,
82
GstM3U8MediaFile *file;
84
file = g_new0 (GstM3U8MediaFile, 1);
87
file->duration = duration;
88
file->sequence = sequence;
94
gst_m3u8_media_file_free (GstM3U8MediaFile * self)
96
g_return_if_fail (self != NULL);
104
int_from_string (gchar * ptr, gchar ** endptr, gint * val)
108
g_return_val_if_fail (ptr != NULL, FALSE);
109
g_return_val_if_fail (val != NULL, FALSE);
112
*val = strtol (ptr, &end, 10);
113
if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN))
114
|| (errno != 0 && *val == 0)) {
115
GST_WARNING ("%s", g_strerror (errno));
126
parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
130
g_return_val_if_fail (ptr != NULL, FALSE);
131
g_return_val_if_fail (*ptr != NULL, FALSE);
132
g_return_val_if_fail (a != NULL, FALSE);
133
g_return_val_if_fail (v != NULL, FALSE);
135
/* [attribute=value,]* */
138
end = p = g_utf8_strchr (*ptr, -1, ',');
141
end = g_utf8_next_char (end);
142
} while (end && *end == ' ');
146
*v = p = g_utf8_strchr (*ptr, -1, '=');
148
*v = g_utf8_next_char (*v);
151
GST_WARNING ("missing = after attribute");
160
_m3u8_compare_uri (GstM3U8 * a, gchar * uri)
162
g_return_val_if_fail (a != NULL, 0);
163
g_return_val_if_fail (uri != NULL, 0);
165
return g_strcmp0 (a->uri, uri);
169
gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
171
return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
175
* @data: a m3u8 playlist text data, taking ownership
178
gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
182
// gboolean discontinuity;
185
g_return_val_if_fail (self != NULL, FALSE);
186
g_return_val_if_fail (data != NULL, FALSE);
187
g_return_val_if_fail (updated != NULL, FALSE);
191
/* check if the data changed since last update */
192
if (self->last_data && g_str_equal (self->last_data, data)) {
193
GST_DEBUG ("Playlist is the same as previous one");
199
if (!g_str_has_prefix (data, "#EXTM3U")) {
200
GST_WARNING ("Data doesn't start with #EXTM3U");
205
g_free (self->last_data);
206
self->last_data = data;
209
g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
210
g_list_free (self->files);
219
end = g_utf8_strchr (data, -1, '\n'); /* FIXME: support \r\n */
223
if (data[0] != '#') {
224
if (duration < 0 && list == NULL) {
225
GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
229
if (!gst_uri_is_valid (data)) {
232
GST_WARNING ("uri not set, can't build a valid uri");
235
slash = g_utf8_strrchr (self->uri, -1, '/');
237
GST_WARNING ("Can't build a valid uri");
242
data = g_strdup_printf ("%s/%s", self->uri, data);
245
data = g_strdup (data);
248
if (g_list_find_custom (self->lists, data,
249
(GCompareFunc) _m3u8_compare_uri)) {
250
GST_DEBUG ("Already have a list with this URI");
251
gst_m3u8_free (list);
254
gst_m3u8_set_uri (list, data);
255
self->lists = g_list_append (self->lists, list);
259
GstM3U8MediaFile *file;
261
gst_m3u8_media_file_new (data, title, duration,
262
self->mediasequence++);
265
self->files = g_list_append (self->files, file);
268
} else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
269
self->endlist = TRUE;
270
} else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
271
if (int_from_string (data + 15, &data, &val))
273
} else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) {
277
GST_WARNING ("Found a list without a uri..., dropping");
278
gst_m3u8_free (list);
281
list = gst_m3u8_new ();
283
while (data && parse_attributes (&data, &a, &v)) {
284
if (g_str_equal (a, "BANDWIDTH")) {
285
if (!int_from_string (v, NULL, &list->bandwidth))
286
GST_WARNING ("Error while reading BANDWIDTH");
287
} else if (g_str_equal (a, "PROGRAM-ID")) {
288
if (!int_from_string (v, NULL, &list->program_id))
289
GST_WARNING ("Error while reading PROGRAM-ID");
290
} else if (g_str_equal (a, "CODECS")) {
291
g_free (list->codecs);
292
list->codecs = g_strdup (v);
293
} else if (g_str_equal (a, "RESOLUTION")) {
294
if (!int_from_string (v, &v, &list->width))
295
GST_WARNING ("Error while reading RESOLUTION width");
296
if (!v || *v != '=') {
297
GST_WARNING ("Missing height");
299
v = g_utf8_next_char (v);
300
if (!int_from_string (v, NULL, &list->height))
301
GST_WARNING ("Error while reading RESOLUTION height");
305
} else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
306
if (int_from_string (data + 22, &data, &val))
307
self->targetduration = val;
308
} else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
309
if (int_from_string (data + 22, &data, &val))
310
self->mediasequence = val;
311
} else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
312
/* discontinuity = TRUE; */
313
} else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
314
/* <YYYY-MM-DDThh:mm:ssZ> */
315
GST_DEBUG ("FIXME parse date");
316
} else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
317
g_free (self->allowcache);
318
self->allowcache = g_strdup (data + 19);
319
} else if (g_str_has_prefix (data, "#EXTINF:")) {
320
if (!int_from_string (data + 8, &data, &val)) {
321
GST_WARNING ("Can't read EXTINF duration");
325
if (duration > self->targetduration)
326
GST_WARNING ("EXTINF duration > TARGETDURATION");
327
if (!data || *data != ',')
329
data = g_utf8_next_char (data);
332
title = g_strdup (data);
335
GST_LOG ("Ignored line: %s", data);
341
data = g_utf8_next_char (end); /* skip \n */
344
/* redorder playlists by bitrate */
347
g_list_sort (self->lists,
348
(GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
354
gst_m3u8_client_new (const gchar * uri)
356
GstM3U8Client *client;
358
g_return_val_if_fail (uri != NULL, NULL);
360
client = g_new0 (GstM3U8Client, 1);
361
client->main = gst_m3u8_new ();
362
client->current = NULL;
363
client->sequence = -1;
364
client->update_failed_count = 0;
365
gst_m3u8_set_uri (client->main, g_strdup (uri));
371
gst_m3u8_client_free (GstM3U8Client * self)
373
g_return_if_fail (self != NULL);
375
gst_m3u8_free (self->main);
380
gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
382
g_return_if_fail (self != NULL);
384
if (m3u8 != self->current) {
385
self->current = m3u8;
386
self->update_failed_count = 0;
391
gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
394
gboolean updated = FALSE;
396
g_return_val_if_fail (self != NULL, FALSE);
398
m3u8 = self->current ? self->current : self->main;
400
if (!gst_m3u8_update (m3u8, data, &updated))
404
self->update_failed_count++;
408
/* select the first playlist, for now */
409
if (!self->current) {
410
if (self->main->lists) {
411
self->current = g_list_first (self->main->lists)->data;
413
self->current = self->main;
417
if (m3u8->files && self->sequence == -1) {
419
GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
420
GST_DEBUG ("Setting first sequence at %d", self->sequence);
427
_find_next (GstM3U8MediaFile * file, GstM3U8Client * client)
429
GST_DEBUG ("Found fragment %d", file->sequence);
430
if (file->sequence >= client->sequence)
436
gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
437
gboolean * discontinuity, const gchar ** uri, GstClockTime * duration)
440
GstM3U8MediaFile *file;
442
g_return_val_if_fail (client != NULL, FALSE);
443
g_return_val_if_fail (client->current != NULL, FALSE);
444
g_return_val_if_fail (discontinuity != NULL, FALSE);
446
GST_DEBUG ("Looking for fragment %d", client->sequence);
447
l = g_list_find_custom (client->current->files, client,
448
(GCompareFunc) _find_next);
452
file = GST_M3U8_MEDIA_FILE (l->data);
454
*discontinuity = client->sequence != file->sequence;
455
client->sequence = file->sequence + 1;
458
*duration = file->duration * GST_SECOND;
463
_sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
465
*duration += self->duration;
469
gst_m3u8_client_get_duration (GstM3U8Client * client)
471
GstClockTime duration = 0;
473
g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
475
/* We can only get the duration for on-demand streams */
476
if (!client->current->endlist)
477
return GST_CLOCK_TIME_NONE;
479
g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
480
return duration * GST_SECOND;
484
gst_m3u8_client_get_uri (GstM3U8Client * client)
486
g_return_val_if_fail (client != NULL, NULL);
488
return client->main->uri;
492
gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
494
g_return_val_if_fail (client != NULL, FALSE);
496
return client->main->lists != NULL;
500
gst_m3u8_client_is_live (GstM3U8Client * client)
502
g_return_val_if_fail (client != NULL, FALSE);
504
if (!client->current || client->current->endlist)