1 /* GStreamer
2 * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
3 * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
4 *
5 * m3u8.c:
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23 #include <stdlib.h>
24 #include <math.h>
25 #include <errno.h>
26 #include <glib.h>
27 #include <string.h>
28
29 #include "m3u8.h"
30 #include "gsthlselements.h"
31
32 #define GST_CAT_DEFAULT hls_debug
33
34 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
35 gchar * title, GstClockTime duration, guint sequence);
36 static void gst_m3u8_init_file_unref (GstM3U8InitFile * self);
37 static gchar *uri_join (const gchar * uri, const gchar * path);
38
39 GstM3U8 *
gst_m3u8_new(void)40 gst_m3u8_new (void)
41 {
42 GstM3U8 *m3u8;
43
44 m3u8 = g_new0 (GstM3U8, 1);
45
46 m3u8->current_file = NULL;
47 m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
48 m3u8->sequence = -1;
49 m3u8->sequence_position = 0;
50 m3u8->highest_sequence_number = -1;
51 m3u8->duration = GST_CLOCK_TIME_NONE;
52
53 g_mutex_init (&m3u8->lock);
54 m3u8->ref_count = 1;
55
56 return m3u8;
57 }
58
59 /* call with M3U8_LOCK held */
60 static void
gst_m3u8_take_uri(GstM3U8 * self,gchar * uri,gchar * base_uri,gchar * name)61 gst_m3u8_take_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
62 {
63 g_return_if_fail (self != NULL);
64
65 if (self->uri != uri) {
66 g_free (self->uri);
67 self->uri = uri;
68 }
69 if (self->base_uri != base_uri) {
70 g_free (self->base_uri);
71 self->base_uri = base_uri;
72 }
73 if (self->name != name) {
74 g_free (self->name);
75 self->name = name;
76 }
77 }
78
79 void
gst_m3u8_set_uri(GstM3U8 * m3u8,const gchar * uri,const gchar * base_uri,const gchar * name)80 gst_m3u8_set_uri (GstM3U8 * m3u8, const gchar * uri, const gchar * base_uri,
81 const gchar * name)
82 {
83 GST_M3U8_LOCK (m3u8);
84 gst_m3u8_take_uri (m3u8, g_strdup (uri), g_strdup (base_uri),
85 g_strdup (name));
86 GST_M3U8_UNLOCK (m3u8);
87 }
88
89 GstM3U8 *
gst_m3u8_ref(GstM3U8 * m3u8)90 gst_m3u8_ref (GstM3U8 * m3u8)
91 {
92 g_assert (m3u8 != NULL && m3u8->ref_count > 0);
93
94 g_atomic_int_add (&m3u8->ref_count, 1);
95 return m3u8;
96 }
97
98 void
gst_m3u8_unref(GstM3U8 * self)99 gst_m3u8_unref (GstM3U8 * self)
100 {
101 g_return_if_fail (self != NULL && self->ref_count > 0);
102
103 if (g_atomic_int_dec_and_test (&self->ref_count)) {
104 g_free (self->uri);
105 g_free (self->base_uri);
106 g_free (self->name);
107
108 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL);
109 g_list_free (self->files);
110
111 g_free (self->last_data);
112 g_mutex_clear (&self->lock);
113 g_free (self);
114 }
115 }
116
117 static GstM3U8MediaFile *
gst_m3u8_media_file_new(gchar * uri,gchar * title,GstClockTime duration,guint sequence)118 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
119 guint sequence)
120 {
121 GstM3U8MediaFile *file;
122
123 file = g_new0 (GstM3U8MediaFile, 1);
124 file->uri = uri;
125 file->title = title;
126 file->duration = duration;
127 file->sequence = sequence;
128 file->ref_count = 1;
129
130 return file;
131 }
132
133 GstM3U8MediaFile *
gst_m3u8_media_file_ref(GstM3U8MediaFile * mfile)134 gst_m3u8_media_file_ref (GstM3U8MediaFile * mfile)
135 {
136 g_assert (mfile != NULL && mfile->ref_count > 0);
137
138 g_atomic_int_add (&mfile->ref_count, 1);
139 return mfile;
140 }
141
142 void
gst_m3u8_media_file_unref(GstM3U8MediaFile * self)143 gst_m3u8_media_file_unref (GstM3U8MediaFile * self)
144 {
145 g_return_if_fail (self != NULL && self->ref_count > 0);
146
147 if (g_atomic_int_dec_and_test (&self->ref_count)) {
148 if (self->init_file)
149 gst_m3u8_init_file_unref (self->init_file);
150 g_free (self->title);
151 g_free (self->uri);
152 g_free (self->key);
153 g_free (self);
154 }
155 }
156
157 static GstM3U8InitFile *
gst_m3u8_init_file_new(gchar * uri)158 gst_m3u8_init_file_new (gchar * uri)
159 {
160 GstM3U8InitFile *file;
161
162 file = g_new0 (GstM3U8InitFile, 1);
163 file->uri = uri;
164 file->ref_count = 1;
165
166 return file;
167 }
168
169 static GstM3U8InitFile *
gst_m3u8_init_file_ref(GstM3U8InitFile * ifile)170 gst_m3u8_init_file_ref (GstM3U8InitFile * ifile)
171 {
172 g_assert (ifile != NULL && ifile->ref_count > 0);
173
174 g_atomic_int_add (&ifile->ref_count, 1);
175 return ifile;
176 }
177
178 static void
gst_m3u8_init_file_unref(GstM3U8InitFile * self)179 gst_m3u8_init_file_unref (GstM3U8InitFile * self)
180 {
181 g_return_if_fail (self != NULL && self->ref_count > 0);
182
183 if (g_atomic_int_dec_and_test (&self->ref_count)) {
184 g_free (self->uri);
185 g_free (self);
186 }
187 }
188
189 static gboolean
int_from_string(gchar * ptr,gchar ** endptr,gint * val)190 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
191 {
192 gchar *end;
193 gint64 ret;
194
195 g_return_val_if_fail (ptr != NULL, FALSE);
196 g_return_val_if_fail (val != NULL, FALSE);
197
198 errno = 0;
199 ret = g_ascii_strtoll (ptr, &end, 10);
200 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
201 || (errno != 0 && ret == 0)) {
202 GST_WARNING ("%s", g_strerror (errno));
203 return FALSE;
204 }
205
206 if (ret > G_MAXINT || ret < G_MININT) {
207 GST_WARNING ("%s", g_strerror (ERANGE));
208 return FALSE;
209 }
210
211 if (endptr)
212 *endptr = end;
213
214 *val = (gint) ret;
215
216 return end != ptr;
217 }
218
219 static gboolean
int64_from_string(gchar * ptr,gchar ** endptr,gint64 * val)220 int64_from_string (gchar * ptr, gchar ** endptr, gint64 * val)
221 {
222 gchar *end;
223 gint64 ret;
224
225 g_return_val_if_fail (ptr != NULL, FALSE);
226 g_return_val_if_fail (val != NULL, FALSE);
227
228 errno = 0;
229 ret = g_ascii_strtoll (ptr, &end, 10);
230 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
231 || (errno != 0 && ret == 0)) {
232 GST_WARNING ("%s", g_strerror (errno));
233 return FALSE;
234 }
235
236 if (endptr)
237 *endptr = end;
238
239 *val = ret;
240
241 return end != ptr;
242 }
243
244 static gboolean
double_from_string(gchar * ptr,gchar ** endptr,gdouble * val)245 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
246 {
247 gchar *end;
248 gdouble ret;
249
250 g_return_val_if_fail (ptr != NULL, FALSE);
251 g_return_val_if_fail (val != NULL, FALSE);
252
253 errno = 0;
254 ret = g_ascii_strtod (ptr, &end);
255 if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
256 || (errno != 0 && ret == 0)) {
257 GST_WARNING ("%s", g_strerror (errno));
258 return FALSE;
259 }
260
261 if (!isfinite (ret)) {
262 GST_WARNING ("%s", g_strerror (ERANGE));
263 return FALSE;
264 }
265
266 if (endptr)
267 *endptr = end;
268
269 *val = (gdouble) ret;
270
271 return end != ptr;
272 }
273
274 static gboolean
parse_attributes(gchar ** ptr,gchar ** a,gchar ** v)275 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
276 {
277 gchar *end = NULL, *p, *ve;
278
279 g_return_val_if_fail (ptr != NULL, FALSE);
280 g_return_val_if_fail (*ptr != NULL, FALSE);
281 g_return_val_if_fail (a != NULL, FALSE);
282 g_return_val_if_fail (v != NULL, FALSE);
283
284 /* [attribute=value,]* */
285
286 *a = *ptr;
287 end = p = g_utf8_strchr (*ptr, -1, ',');
288 if (end) {
289 gchar *q = g_utf8_strchr (*ptr, -1, '"');
290 if (q && q < end) {
291 /* special case, such as CODECS="avc1.77.30, mp4a.40.2" */
292 q = g_utf8_next_char (q);
293 if (q) {
294 q = g_utf8_strchr (q, -1, '"');
295 }
296 if (q) {
297 end = p = g_utf8_strchr (q, -1, ',');
298 }
299 }
300 }
301 if (end) {
302 do {
303 end = g_utf8_next_char (end);
304 } while (end && *end == ' ');
305 *p = '\0';
306 }
307
308 *v = p = g_utf8_strchr (*ptr, -1, '=');
309 if (*v) {
310 *p = '\0';
311 *v = g_utf8_next_char (*v);
312 if (**v == '"') {
313 ve = g_utf8_next_char (*v);
314 if (ve) {
315 ve = g_utf8_strchr (ve, -1, '"');
316 }
317 if (ve) {
318 *v = g_utf8_next_char (*v);
319 *ve = '\0';
320 } else {
321 GST_WARNING ("Cannot remove quotation marks from %s", *a);
322 }
323 }
324 } else {
325 GST_WARNING ("missing = after attribute");
326 return FALSE;
327 }
328
329 *ptr = end;
330 return TRUE;
331 }
332
333 static gint
gst_hls_variant_stream_compare_by_bitrate(gconstpointer a,gconstpointer b)334 gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b)
335 {
336 const GstHLSVariantStream *vs_a = (const GstHLSVariantStream *) a;
337 const GstHLSVariantStream *vs_b = (const GstHLSVariantStream *) b;
338
339 if (vs_a->bandwidth == vs_b->bandwidth)
340 return g_strcmp0 (vs_a->name, vs_b->name);
341
342 return vs_a->bandwidth - vs_b->bandwidth;
343 }
344
345 /* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
346 * the client SHOULD halt playback (6.3.4), which is what we do then. */
347 static gboolean
check_media_seqnums(GstM3U8 * self,GList * previous_files)348 check_media_seqnums (GstM3U8 * self, GList * previous_files)
349 {
350 GList *l, *m;
351 GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
352
353 g_return_val_if_fail (previous_files, FALSE);
354
355 if (!self->files) {
356 /* Empty playlists are trivially consistent */
357 return TRUE;
358 }
359
360 /* Find first case of higher/equal sequence number in new playlist.
361 * From there on we can linearly step ahead */
362 for (l = self->files; l; l = l->next) {
363 gboolean match = FALSE;
364
365 f1 = l->data;
366 for (m = previous_files; m; m = m->next) {
367 f2 = m->data;
368
369 if (f1->sequence >= f2->sequence) {
370 match = TRUE;
371 break;
372 }
373 }
374 if (match)
375 break;
376 }
377
378 /* We must have seen at least one entry on each list */
379 g_assert (f1 != NULL);
380 g_assert (f2 != NULL);
381
382 if (!l) {
383 /* No match, no sequence in the new playlist was higher than
384 * any in the old. This is bad! */
385 GST_ERROR ("Media sequence doesn't continue: last new %" G_GINT64_FORMAT
386 " < last old %" G_GINT64_FORMAT, f1->sequence, f2->sequence);
387 return FALSE;
388 }
389
390 for (; l && m; l = l->next, m = m->next) {
391 f1 = l->data;
392 f2 = m->data;
393
394 if (f1->sequence == f2->sequence && !g_str_equal (f1->uri, f2->uri)) {
395 /* Same sequence, different URI. This is bad! */
396 GST_ERROR ("Media URIs inconsistent (sequence %" G_GINT64_FORMAT
397 "): had '%s', got '%s'", f1->sequence, f2->uri, f1->uri);
398 return FALSE;
399 } else if (f1->sequence < f2->sequence) {
400 /* Not same sequence but by construction sequence must be higher in the
401 * new one. All good in that case, if it isn't then this means that
402 * sequence numbers are decreasing or files were inserted */
403 GST_ERROR ("Media sequences inconsistent: %" G_GINT64_FORMAT " < %"
404 G_GINT64_FORMAT ": URIs new '%s' old '%s'", f1->sequence,
405 f2->sequence, f1->uri, f2->uri);
406 return FALSE;
407 }
408 }
409
410 /* All good if we're getting here */
411 return TRUE;
412 }
413
414 /* If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
415 * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
416 * playlist in relation to the old. That is, same URIs get the same number
417 * and later URIs get higher numbers */
418 static void
generate_media_seqnums(GstM3U8 * self,GList * previous_files)419 generate_media_seqnums (GstM3U8 * self, GList * previous_files)
420 {
421 GList *l, *m;
422 GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
423 gint64 mediasequence;
424
425 g_return_if_fail (previous_files);
426
427 /* Find first case of same URI in new playlist.
428 * From there on we can linearly step ahead */
429 for (l = self->files; l; l = l->next) {
430 gboolean match = FALSE;
431
432 f1 = l->data;
433 for (m = previous_files; m; m = m->next) {
434 f2 = m->data;
435
436 if (g_str_equal (f1->uri, f2->uri)) {
437 match = TRUE;
438 break;
439 }
440 }
441
442 if (match)
443 break;
444 }
445
446 if (l) {
447 /* Match, check that all following ones are matching too and continue
448 * sequence numbers from there on */
449
450 mediasequence = f2->sequence;
451
452 for (; l && m; l = l->next, m = m->next) {
453 f1 = l->data;
454 f2 = m->data;
455
456 f1->sequence = mediasequence;
457 mediasequence++;
458
459 if (!g_str_equal (f1->uri, f2->uri)) {
460 GST_WARNING ("Inconsistent URIs after playlist update: '%s' != '%s'",
461 f1->uri, f2->uri);
462 }
463 }
464 } else {
465 /* No match, this means f2 is the last item in the previous playlist
466 * and we have to start our new playlist at that sequence */
467 mediasequence = f2->sequence + 1;
468 l = self->files;
469 }
470
471 for (; l; l = l->next) {
472 f1 = l->data;
473
474 f1->sequence = mediasequence;
475 mediasequence++;
476 }
477 }
478
479 /*
480 * @data: a m3u8 playlist text data, taking ownership
481 */
482 gboolean
gst_m3u8_update(GstM3U8 * self,gchar * data)483 gst_m3u8_update (GstM3U8 * self, gchar * data)
484 {
485 gint val;
486 GstClockTime duration;
487 gchar *title, *end;
488 gboolean discontinuity = FALSE;
489 gchar *current_key = NULL;
490 gboolean have_iv = FALSE;
491 guint8 iv[16] = { 0, };
492 gint64 size = -1, offset = -1;
493 gint64 mediasequence;
494 GList *previous_files = NULL;
495 gboolean have_mediasequence = FALSE;
496 GstM3U8InitFile *last_init_file = NULL;
497
498 g_return_val_if_fail (self != NULL, FALSE);
499 g_return_val_if_fail (data != NULL, FALSE);
500
501 GST_M3U8_LOCK (self);
502
503 /* check if the data changed since last update */
504 if (self->last_data && g_str_equal (self->last_data, data)) {
505 GST_DEBUG ("Playlist is the same as previous one");
506 g_free (data);
507 GST_M3U8_UNLOCK (self);
508 return TRUE;
509 }
510
511 if (!g_str_has_prefix (data, "#EXTM3U")) {
512 GST_WARNING ("Data doesn't start with #EXTM3U");
513 g_free (data);
514 GST_M3U8_UNLOCK (self);
515 return FALSE;
516 }
517
518 if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) {
519 GST_WARNING ("Not a media playlist, but a master playlist!");
520 GST_M3U8_UNLOCK (self);
521 return FALSE;
522 }
523
524 GST_TRACE ("data:\n%s", data);
525
526 g_free (self->last_data);
527 self->last_data = data;
528
529 self->current_file = NULL;
530 previous_files = self->files;
531 self->files = NULL;
532 self->duration = GST_CLOCK_TIME_NONE;
533 mediasequence = 0;
534
535 /* By default, allow caching */
536 self->allowcache = TRUE;
537
538 duration = 0;
539 title = NULL;
540 data += 7;
541 while (TRUE) {
542 gchar *r;
543
544 end = g_utf8_strchr (data, -1, '\n');
545 if (end)
546 *end = '\0';
547
548 r = g_utf8_strchr (data, -1, '\r');
549 if (r)
550 *r = '\0';
551
552 if (data[0] != '#' && data[0] != '\0') {
553 if (duration <= 0) {
554 GST_LOG ("%s: got line without EXTINF, dropping", data);
555 goto next_line;
556 }
557
558 data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
559 if (data != NULL) {
560 GstM3U8MediaFile *file;
561 file = gst_m3u8_media_file_new (data, title, duration, mediasequence++);
562
563 /* set encryption params */
564 file->key = current_key ? g_strdup (current_key) : NULL;
565 if (file->key) {
566 if (have_iv) {
567 memcpy (file->iv, iv, sizeof (iv));
568 } else {
569 guint8 *iv = file->iv + 12;
570 GST_WRITE_UINT32_BE (iv, file->sequence);
571 }
572 }
573
574 if (size != -1) {
575 file->size = size;
576 if (offset != -1) {
577 file->offset = offset;
578 } else {
579 GstM3U8MediaFile *prev = self->files ? self->files->data : NULL;
580
581 if (!prev) {
582 offset = 0;
583 } else {
584 offset = prev->offset + prev->size;
585 }
586 file->offset = offset;
587 }
588 } else {
589 file->size = -1;
590 file->offset = 0;
591 }
592
593 file->discont = discontinuity;
594 if (last_init_file)
595 file->init_file = gst_m3u8_init_file_ref (last_init_file);
596
597 duration = 0;
598 title = NULL;
599 discontinuity = FALSE;
600 size = offset = -1;
601 self->files = g_list_prepend (self->files, file);
602 }
603
604 } else if (g_str_has_prefix (data, "#EXTINF:")) {
605 gdouble fval;
606 if (!double_from_string (data + 8, &data, &fval)) {
607 GST_WARNING ("Can't read EXTINF duration");
608 goto next_line;
609 }
610 duration = fval * (gdouble) GST_SECOND;
611 if (self->targetduration > 0 && duration > self->targetduration) {
612 GST_WARNING ("EXTINF duration (%" GST_TIME_FORMAT
613 ") > TARGETDURATION (%" GST_TIME_FORMAT ")",
614 GST_TIME_ARGS (duration), GST_TIME_ARGS (self->targetduration));
615 }
616 if (!data || *data != ',')
617 goto next_line;
618 data = g_utf8_next_char (data);
619 if (data != end) {
620 g_free (title);
621 title = g_strdup (data);
622 }
623 } else if (g_str_has_prefix (data, "#EXT-X-")) {
624 gchar *data_ext_x = data + 7;
625
626 /* All these entries start with #EXT-X- */
627 if (g_str_has_prefix (data_ext_x, "ENDLIST")) {
628 self->endlist = TRUE;
629 } else if (g_str_has_prefix (data_ext_x, "VERSION:")) {
630 if (int_from_string (data + 15, &data, &val))
631 self->version = val;
632 } else if (g_str_has_prefix (data_ext_x, "TARGETDURATION:")) {
633 if (int_from_string (data + 22, &data, &val))
634 self->targetduration = val * GST_SECOND;
635 } else if (g_str_has_prefix (data_ext_x, "MEDIA-SEQUENCE:")) {
636 if (int_from_string (data + 22, &data, &val)) {
637 mediasequence = val;
638 have_mediasequence = TRUE;
639 }
640 } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY-SEQUENCE:")) {
641 if (int_from_string (data + 30, &data, &val)
642 && val != self->discont_sequence) {
643 self->discont_sequence = val;
644 discontinuity = TRUE;
645 }
646 } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY")) {
647 self->discont_sequence++;
648 discontinuity = TRUE;
649 } else if (g_str_has_prefix (data_ext_x, "PROGRAM-DATE-TIME:")) {
650 /* <YYYY-MM-DDThh:mm:ssZ> */
651 GST_DEBUG ("FIXME parse date");
652 } else if (g_str_has_prefix (data_ext_x, "ALLOW-CACHE:")) {
653 self->allowcache = g_ascii_strcasecmp (data + 19, "YES") == 0;
654 } else if (g_str_has_prefix (data_ext_x, "KEY:")) {
655 gchar *v, *a;
656
657 data = data + 11;
658
659 /* IV and KEY are only valid until the next #EXT-X-KEY */
660 have_iv = FALSE;
661 g_free (current_key);
662 current_key = NULL;
663 while (data && parse_attributes (&data, &a, &v)) {
664 if (g_str_equal (a, "URI")) {
665 current_key =
666 uri_join (self->base_uri ? self->base_uri : self->uri, v);
667 } else if (g_str_equal (a, "IV")) {
668 gchar *ivp = v;
669 gint i;
670
671 if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
672 && !g_str_has_prefix (ivp, "0X"))) {
673 GST_WARNING ("Can't read IV");
674 continue;
675 }
676
677 ivp += 2;
678 for (i = 0; i < 16; i++) {
679 gint h, l;
680
681 h = g_ascii_xdigit_value (*ivp);
682 ivp++;
683 l = g_ascii_xdigit_value (*ivp);
684 ivp++;
685 if (h == -1 || l == -1) {
686 i = -1;
687 break;
688 }
689 iv[i] = (h << 4) | l;
690 }
691
692 if (i == -1) {
693 GST_WARNING ("Can't read IV");
694 continue;
695 }
696 have_iv = TRUE;
697 } else if (g_str_equal (a, "METHOD")) {
698 if (!g_str_equal (v, "AES-128")) {
699 GST_WARNING ("Encryption method %s not supported", v);
700 continue;
701 }
702 }
703 }
704 } else if (g_str_has_prefix (data_ext_x, "BYTERANGE:")) {
705 gchar *v = data + 17;
706
707 if (int64_from_string (v, &v, &size)) {
708 if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
709 goto next_line;
710 } else {
711 goto next_line;
712 }
713 } else if (g_str_has_prefix (data_ext_x, "MAP:")) {
714 gchar *v, *a, *header_uri = NULL;
715
716 data = data + 11;
717
718 while (data != NULL && parse_attributes (&data, &a, &v)) {
719 if (strcmp (a, "URI") == 0) {
720 header_uri =
721 uri_join (self->base_uri ? self->base_uri : self->uri, v);
722 } else if (strcmp (a, "BYTERANGE") == 0) {
723 if (int64_from_string (v, &v, &size)) {
724 if (*v == '@' && !int64_from_string (v + 1, &v, &offset)) {
725 g_free (header_uri);
726 goto next_line;
727 }
728 } else {
729 g_free (header_uri);
730 goto next_line;
731 }
732 }
733 }
734
735 if (header_uri) {
736 GstM3U8InitFile *init_file;
737 init_file = gst_m3u8_init_file_new (header_uri);
738
739 if (size != -1) {
740 init_file->size = size;
741 if (offset != -1)
742 init_file->offset = offset;
743 else
744 init_file->offset = 0;
745 } else {
746 init_file->size = -1;
747 init_file->offset = 0;
748 }
749 if (last_init_file)
750 gst_m3u8_init_file_unref (last_init_file);
751
752 last_init_file = init_file;
753 }
754 } else {
755 GST_LOG ("Ignored line: %s", data);
756 }
757 } else {
758 GST_LOG ("Ignored line: %s", data);
759 }
760
761 next_line:
762 if (!end)
763 break;
764 data = g_utf8_next_char (end); /* skip \n */
765 }
766
767 g_free (current_key);
768 current_key = NULL;
769
770 self->files = g_list_reverse (self->files);
771
772 if (last_init_file)
773 gst_m3u8_init_file_unref (last_init_file);
774
775 if (previous_files) {
776 gboolean consistent = TRUE;
777
778 if (have_mediasequence) {
779 consistent = check_media_seqnums (self, previous_files);
780 } else {
781 generate_media_seqnums (self, previous_files);
782 }
783
784 g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL);
785 g_list_free (previous_files);
786 previous_files = NULL;
787
788 /* error was reported above already */
789 if (!consistent) {
790 GST_M3U8_UNLOCK (self);
791 return FALSE;
792 }
793 }
794
795 if (self->files == NULL) {
796 GST_ERROR ("Invalid media playlist, it does not contain any media files");
797 GST_M3U8_UNLOCK (self);
798 return FALSE;
799 }
800
801 /* calculate the start and end times of this media playlist. */
802 {
803 GList *walk;
804 GstM3U8MediaFile *file;
805 GstClockTime duration = 0;
806
807 mediasequence = -1;
808
809 for (walk = self->files; walk; walk = walk->next) {
810 file = walk->data;
811
812 if (mediasequence == -1) {
813 mediasequence = file->sequence;
814 } else if (mediasequence >= file->sequence) {
815 GST_ERROR ("Non-increasing media sequence");
816 GST_M3U8_UNLOCK (self);
817 return FALSE;
818 } else {
819 mediasequence = file->sequence;
820 }
821
822 duration += file->duration;
823 if (file->sequence > self->highest_sequence_number) {
824 if (self->highest_sequence_number >= 0) {
825 /* if an update of the media playlist has been missed, there
826 will be a gap between self->highest_sequence_number and the
827 first sequence number in this media playlist. In this situation
828 assume that the missing fragments had a duration of
829 targetduration each */
830 self->last_file_end +=
831 (file->sequence - self->highest_sequence_number -
832 1) * self->targetduration;
833 }
834 self->last_file_end += file->duration;
835 self->highest_sequence_number = file->sequence;
836 }
837 }
838 if (GST_M3U8_IS_LIVE (self)) {
839 self->first_file_start = self->last_file_end - duration;
840 GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %"
841 GST_TIME_FORMAT, GST_TIME_ARGS (self->first_file_start),
842 GST_TIME_ARGS (self->last_file_end));
843 }
844 self->duration = duration;
845 }
846
847 /* first-time setup */
848 if (self->files && self->sequence == -1) {
849 GList *file;
850
851 if (GST_M3U8_IS_LIVE (self)) {
852 gint i;
853 GstClockTime sequence_pos = 0;
854
855 file = g_list_last (self->files);
856
857 if (self->last_file_end >= GST_M3U8_MEDIA_FILE (file->data)->duration) {
858 sequence_pos =
859 self->last_file_end - GST_M3U8_MEDIA_FILE (file->data)->duration;
860 }
861
862 /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
863 * the end of the playlist. See section 6.3.3 of HLS draft */
864 for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE && file->prev &&
865 GST_M3U8_MEDIA_FILE (file->prev->data)->duration <= sequence_pos;
866 ++i) {
867 file = file->prev;
868 sequence_pos -= GST_M3U8_MEDIA_FILE (file->data)->duration;
869 }
870 self->sequence_position = sequence_pos;
871 } else {
872 file = g_list_first (self->files);
873 self->sequence_position = 0;
874 }
875 self->current_file = file;
876 self->sequence = GST_M3U8_MEDIA_FILE (file->data)->sequence;
877 GST_DEBUG ("first sequence: %u", (guint) self->sequence);
878 }
879
880 GST_LOG ("processed media playlist %s, %u fragments", self->name,
881 g_list_length (self->files));
882
883 GST_M3U8_UNLOCK (self);
884
885 return TRUE;
886 }
887
888 /* call with M3U8_LOCK held */
889 static GList *
m3u8_find_next_fragment(GstM3U8 * m3u8,gboolean forward)890 m3u8_find_next_fragment (GstM3U8 * m3u8, gboolean forward)
891 {
892 GstM3U8MediaFile *file;
893 GList *l = m3u8->files;
894
895 if (forward) {
896 while (l) {
897 file = l->data;
898
899 if (file->sequence >= m3u8->sequence)
900 break;
901
902 l = l->next;
903 }
904 } else {
905 l = g_list_last (l);
906
907 while (l) {
908 file = l->data;
909
910 if (file->sequence <= m3u8->sequence)
911 break;
912
913 l = l->prev;
914 }
915 }
916
917 return l;
918 }
919
920 GstM3U8MediaFile *
gst_m3u8_get_next_fragment(GstM3U8 * m3u8,gboolean forward,GstClockTime * sequence_position,gboolean * discont)921 gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward,
922 GstClockTime * sequence_position, gboolean * discont)
923 {
924 GstM3U8MediaFile *file = NULL;
925
926 g_return_val_if_fail (m3u8 != NULL, NULL);
927
928 GST_M3U8_LOCK (m3u8);
929
930 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
931
932 if (m3u8->sequence < 0) /* can't happen really */
933 goto out;
934
935 if (m3u8->current_file == NULL)
936 m3u8->current_file = m3u8_find_next_fragment (m3u8, forward);
937
938 if (m3u8->current_file == NULL)
939 goto out;
940
941 file = gst_m3u8_media_file_ref (m3u8->current_file->data);
942
943 GST_DEBUG ("Got fragment with sequence %u (current sequence %u)",
944 (guint) file->sequence, (guint) m3u8->sequence);
945
946 if (sequence_position)
947 *sequence_position = m3u8->sequence_position;
948 if (discont)
949 *discont = file->discont || (m3u8->sequence != file->sequence);
950
951 m3u8->current_file_duration = file->duration;
952 m3u8->sequence = file->sequence;
953
954 out:
955
956 GST_M3U8_UNLOCK (m3u8);
957
958 return file;
959 }
960
961 gboolean
gst_m3u8_has_next_fragment(GstM3U8 * m3u8,gboolean forward)962 gst_m3u8_has_next_fragment (GstM3U8 * m3u8, gboolean forward)
963 {
964 gboolean have_next;
965 GList *cur;
966
967 g_return_val_if_fail (m3u8 != NULL, FALSE);
968
969 GST_M3U8_LOCK (m3u8);
970
971 GST_DEBUG ("Checking next fragment %" G_GINT64_FORMAT,
972 m3u8->sequence + (forward ? 1 : -1));
973
974 if (m3u8->current_file) {
975 cur = m3u8->current_file;
976 } else {
977 cur = m3u8_find_next_fragment (m3u8, forward);
978 }
979
980 have_next = cur && ((forward && cur->next) || (!forward && cur->prev));
981
982 GST_M3U8_UNLOCK (m3u8);
983
984 return have_next;
985 }
986
987 /* call with M3U8_LOCK held */
988 static void
m3u8_alternate_advance(GstM3U8 * m3u8,gboolean forward)989 m3u8_alternate_advance (GstM3U8 * m3u8, gboolean forward)
990 {
991 gint targetnum = m3u8->sequence;
992 GList *tmp;
993 GstM3U8MediaFile *mf;
994
995 /* figure out the target seqnum */
996 if (forward)
997 targetnum += 1;
998 else
999 targetnum -= 1;
1000
1001 for (tmp = m3u8->files; tmp; tmp = tmp->next) {
1002 mf = (GstM3U8MediaFile *) tmp->data;
1003 if (mf->sequence == targetnum)
1004 break;
1005 }
1006 if (tmp == NULL) {
1007 GST_WARNING ("Can't find next fragment");
1008 return;
1009 }
1010 m3u8->current_file = tmp;
1011 m3u8->sequence = targetnum;
1012 m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (tmp->data)->duration;
1013 }
1014
1015 void
gst_m3u8_advance_fragment(GstM3U8 * m3u8,gboolean forward)1016 gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
1017 {
1018 GstM3U8MediaFile *file;
1019
1020 g_return_if_fail (m3u8 != NULL);
1021
1022 GST_M3U8_LOCK (m3u8);
1023
1024 GST_DEBUG ("Sequence position was %" GST_TIME_FORMAT,
1025 GST_TIME_ARGS (m3u8->sequence_position));
1026 if (GST_CLOCK_TIME_IS_VALID (m3u8->current_file_duration)) {
1027 /* Advance our position based on the previous fragment we played */
1028 if (forward)
1029 m3u8->sequence_position += m3u8->current_file_duration;
1030 else if (m3u8->current_file_duration < m3u8->sequence_position)
1031 m3u8->sequence_position -= m3u8->current_file_duration;
1032 else
1033 m3u8->sequence_position = 0;
1034 m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
1035 GST_DEBUG ("Sequence position now %" GST_TIME_FORMAT,
1036 GST_TIME_ARGS (m3u8->sequence_position));
1037 }
1038 if (!m3u8->current_file) {
1039 GList *l;
1040
1041 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
1042 for (l = m3u8->files; l != NULL; l = l->next) {
1043 if (GST_M3U8_MEDIA_FILE (l->data)->sequence == m3u8->sequence) {
1044 m3u8->current_file = l;
1045 break;
1046 }
1047 }
1048 if (m3u8->current_file == NULL) {
1049 GST_DEBUG
1050 ("Could not find current fragment, trying next fragment directly");
1051 m3u8_alternate_advance (m3u8, forward);
1052
1053 /* Resync sequence number if the above has failed for live streams */
1054 if (m3u8->current_file == NULL && GST_M3U8_IS_LIVE (m3u8)) {
1055 /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
1056 the end of the playlist. See section 6.3.3 of HLS draft */
1057 gint pos =
1058 g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
1059 m3u8->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
1060 m3u8->current_file_duration =
1061 GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
1062
1063 GST_WARNING ("Resyncing live playlist");
1064 }
1065 goto out;
1066 }
1067 }
1068
1069 file = GST_M3U8_MEDIA_FILE (m3u8->current_file->data);
1070 GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
1071 if (forward) {
1072 m3u8->current_file = m3u8->current_file->next;
1073 if (m3u8->current_file) {
1074 m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
1075 } else {
1076 m3u8->sequence = file->sequence + 1;
1077 }
1078 } else {
1079 m3u8->current_file = m3u8->current_file->prev;
1080 if (m3u8->current_file) {
1081 m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
1082 } else {
1083 m3u8->sequence = file->sequence - 1;
1084 }
1085 }
1086 if (m3u8->current_file) {
1087 /* Store duration of the fragment we're using to update the position
1088 * the next time we advance */
1089 m3u8->current_file_duration =
1090 GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
1091 }
1092
1093 out:
1094
1095 GST_M3U8_UNLOCK (m3u8);
1096 }
1097
1098 GstClockTime
gst_m3u8_get_duration(GstM3U8 * m3u8)1099 gst_m3u8_get_duration (GstM3U8 * m3u8)
1100 {
1101 GstClockTime duration = GST_CLOCK_TIME_NONE;
1102
1103 g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1104
1105 GST_M3U8_LOCK (m3u8);
1106
1107 /* We can only get the duration for on-demand streams */
1108 if (!m3u8->endlist)
1109 goto out;
1110
1111 if (!GST_CLOCK_TIME_IS_VALID (m3u8->duration) && m3u8->files != NULL) {
1112 GList *f;
1113
1114 m3u8->duration = 0;
1115 for (f = m3u8->files; f != NULL; f = f->next)
1116 m3u8->duration += GST_M3U8_MEDIA_FILE (f)->duration;
1117 }
1118 duration = m3u8->duration;
1119
1120 out:
1121
1122 GST_M3U8_UNLOCK (m3u8);
1123
1124 return duration;
1125 }
1126
1127 GstClockTime
gst_m3u8_get_target_duration(GstM3U8 * m3u8)1128 gst_m3u8_get_target_duration (GstM3U8 * m3u8)
1129 {
1130 GstClockTime target_duration;
1131
1132 g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1133
1134 GST_M3U8_LOCK (m3u8);
1135 target_duration = m3u8->targetduration;
1136 GST_M3U8_UNLOCK (m3u8);
1137
1138 return target_duration;
1139 }
1140
1141 gchar *
gst_m3u8_get_uri(GstM3U8 * m3u8)1142 gst_m3u8_get_uri (GstM3U8 * m3u8)
1143 {
1144 gchar *uri;
1145
1146 GST_M3U8_LOCK (m3u8);
1147 uri = g_strdup (m3u8->uri);
1148 GST_M3U8_UNLOCK (m3u8);
1149
1150 return uri;
1151 }
1152
1153 gboolean
gst_m3u8_is_live(GstM3U8 * m3u8)1154 gst_m3u8_is_live (GstM3U8 * m3u8)
1155 {
1156 gboolean is_live;
1157
1158 g_return_val_if_fail (m3u8 != NULL, FALSE);
1159
1160 GST_M3U8_LOCK (m3u8);
1161 is_live = GST_M3U8_IS_LIVE (m3u8);
1162 GST_M3U8_UNLOCK (m3u8);
1163
1164 return is_live;
1165 }
1166
1167 gchar *
uri_join(const gchar * uri1,const gchar * uri2)1168 uri_join (const gchar * uri1, const gchar * uri2)
1169 {
1170 gchar *uri_copy, *tmp, *ret = NULL;
1171
1172 if (gst_uri_is_valid (uri2))
1173 return g_strdup (uri2);
1174
1175 uri_copy = g_strdup (uri1);
1176 if (uri2[0] != '/') {
1177 /* uri2 is a relative uri2 */
1178 /* look for query params */
1179 tmp = g_utf8_strchr (uri_copy, -1, '?');
1180 if (tmp) {
1181 /* find last / char, ignoring query params */
1182 tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
1183 } else {
1184 /* find last / char in URL */
1185 tmp = g_utf8_strrchr (uri_copy, -1, '/');
1186 }
1187 if (!tmp) {
1188 GST_WARNING ("Can't build a valid uri_copy");
1189 goto out;
1190 }
1191
1192 *tmp = '\0';
1193 ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
1194 } else {
1195 /* uri2 is an absolute uri2 */
1196 char *scheme, *hostname;
1197
1198 scheme = uri_copy;
1199 /* find the : in <scheme>:// */
1200 tmp = g_utf8_strchr (uri_copy, -1, ':');
1201 if (!tmp) {
1202 GST_WARNING ("Can't build a valid uri_copy");
1203 goto out;
1204 }
1205
1206 *tmp = '\0';
1207
1208 /* skip :// */
1209 hostname = tmp + 3;
1210
1211 tmp = g_utf8_strchr (hostname, -1, '/');
1212 if (tmp)
1213 *tmp = '\0';
1214
1215 ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
1216 }
1217
1218 out:
1219 g_free (uri_copy);
1220 return ret;
1221 }
1222
1223 gboolean
gst_m3u8_get_seek_range(GstM3U8 * m3u8,gint64 * start,gint64 * stop)1224 gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop)
1225 {
1226 GstClockTime duration = 0;
1227 GList *walk;
1228 GstM3U8MediaFile *file;
1229 guint count;
1230 guint min_distance = 0;
1231
1232 g_return_val_if_fail (m3u8 != NULL, FALSE);
1233
1234 GST_M3U8_LOCK (m3u8);
1235
1236 if (m3u8->files == NULL)
1237 goto out;
1238
1239 if (GST_M3U8_IS_LIVE (m3u8)) {
1240 /* min_distance is used to make sure the seek range is never closer than
1241 GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of a live
1242 playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
1243 min_distance = GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
1244 }
1245 count = g_list_length (m3u8->files);
1246
1247 for (walk = m3u8->files; walk && count > min_distance; walk = walk->next) {
1248 file = walk->data;
1249 --count;
1250 duration += file->duration;
1251 }
1252
1253 if (duration <= 0)
1254 goto out;
1255
1256 *start = m3u8->first_file_start;
1257 *stop = *start + duration;
1258
1259 out:
1260
1261 GST_M3U8_UNLOCK (m3u8);
1262 return (duration > 0);
1263 }
1264
1265 GstHLSMedia *
gst_hls_media_ref(GstHLSMedia * media)1266 gst_hls_media_ref (GstHLSMedia * media)
1267 {
1268 g_assert (media != NULL && media->ref_count > 0);
1269 g_atomic_int_add (&media->ref_count, 1);
1270 return media;
1271 }
1272
1273 void
gst_hls_media_unref(GstHLSMedia * media)1274 gst_hls_media_unref (GstHLSMedia * media)
1275 {
1276 g_assert (media != NULL && media->ref_count > 0);
1277 if (g_atomic_int_dec_and_test (&media->ref_count)) {
1278 if (media->playlist)
1279 gst_m3u8_unref (media->playlist);
1280 g_free (media->group_id);
1281 g_free (media->name);
1282 g_free (media->uri);
1283 g_free (media->lang);
1284 g_free (media);
1285 }
1286 }
1287
1288 static GstHLSMediaType
gst_m3u8_get_hls_media_type_from_string(const gchar * type_name)1289 gst_m3u8_get_hls_media_type_from_string (const gchar * type_name)
1290 {
1291 if (strcmp (type_name, "AUDIO") == 0)
1292 return GST_HLS_MEDIA_TYPE_AUDIO;
1293 if (strcmp (type_name, "VIDEO") == 0)
1294 return GST_HLS_MEDIA_TYPE_VIDEO;
1295 if (strcmp (type_name, "SUBTITLES") == 0)
1296 return GST_HLS_MEDIA_TYPE_SUBTITLES;
1297 if (strcmp (type_name, "CLOSED_CAPTIONS") == 0)
1298 return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS;
1299
1300 return GST_HLS_MEDIA_TYPE_INVALID;
1301 }
1302
1303 #define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_hls_media_type_get_name(mtype)
1304 const gchar *
gst_hls_media_type_get_name(GstHLSMediaType mtype)1305 gst_hls_media_type_get_name (GstHLSMediaType mtype)
1306 {
1307 static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video",
1308 "subtitle", "closed-captions"
1309 };
1310
1311 if (mtype < 0 || mtype >= GST_HLS_N_MEDIA_TYPES)
1312 return "invalid";
1313
1314 return nicks[mtype];
1315 }
1316
1317 /* returns unquoted copy of string */
1318 static gchar *
gst_m3u8_unquote(const gchar * str)1319 gst_m3u8_unquote (const gchar * str)
1320 {
1321 const gchar *start, *end;
1322
1323 start = strchr (str, '"');
1324 if (start == NULL)
1325 return g_strdup (str);
1326 end = strchr (start + 1, '"');
1327 if (end == NULL) {
1328 GST_WARNING ("Broken quoted string [%s] - can't find end quote", str);
1329 return g_strdup (start + 1);
1330 }
1331 return g_strndup (start + 1, (gsize) (end - (start + 1)));
1332 }
1333
1334 static GstHLSMedia *
gst_m3u8_parse_media(gchar * desc,const gchar * base_uri)1335 gst_m3u8_parse_media (gchar * desc, const gchar * base_uri)
1336 {
1337 GstHLSMedia *media;
1338 gchar *a, *v;
1339
1340 media = g_new0 (GstHLSMedia, 1);
1341 media->ref_count = 1;
1342 media->playlist = gst_m3u8_new ();
1343 media->mtype = GST_HLS_MEDIA_TYPE_INVALID;
1344
1345 GST_LOG ("parsing %s", desc);
1346 while (desc != NULL && parse_attributes (&desc, &a, &v)) {
1347 if (strcmp (a, "TYPE") == 0) {
1348 media->mtype = gst_m3u8_get_hls_media_type_from_string (v);
1349 } else if (strcmp (a, "GROUP-ID") == 0) {
1350 g_free (media->group_id);
1351 media->group_id = gst_m3u8_unquote (v);
1352 } else if (strcmp (a, "NAME") == 0) {
1353 g_free (media->name);
1354 media->name = gst_m3u8_unquote (v);
1355 } else if (strcmp (a, "URI") == 0) {
1356 gchar *uri;
1357
1358 g_free (media->uri);
1359 uri = gst_m3u8_unquote (v);
1360 media->uri = uri_join (base_uri, uri);
1361 g_free (uri);
1362 } else if (strcmp (a, "LANGUAGE") == 0) {
1363 g_free (media->lang);
1364 media->lang = gst_m3u8_unquote (v);
1365 } else if (strcmp (a, "DEFAULT") == 0) {
1366 media->is_default = g_ascii_strcasecmp (v, "yes") == 0;
1367 } else if (strcmp (a, "FORCED") == 0) {
1368 media->forced = g_ascii_strcasecmp (v, "yes") == 0;
1369 } else if (strcmp (a, "AUTOSELECT") == 0) {
1370 media->autoselect = g_ascii_strcasecmp (v, "yes") == 0;
1371 } else {
1372 /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */
1373 GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v);
1374 }
1375 }
1376
1377 if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID)
1378 goto required_attributes_missing;
1379
1380 if (media->uri == NULL)
1381 goto existing_stream;
1382
1383 if (media->group_id == NULL || media->name == NULL)
1384 goto required_attributes_missing;
1385
1386 if (media->mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS)
1387 goto uri_with_cc;
1388
1389 GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s",
1390 GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name,
1391 media->uri, media->is_default ? "default" : "-",
1392 media->autoselect ? "autoselect" : "-",
1393 media->forced ? "forced" : "-", media->lang ? media->lang : "??");
1394
1395 return media;
1396
1397 uri_with_cc:
1398 {
1399 GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified");
1400 goto out_error;
1401 }
1402 required_attributes_missing:
1403 {
1404 GST_WARNING ("EXT-X-MEDIA description is missing required attributes");
1405 goto out_error;
1406 /* fall through */
1407 }
1408 existing_stream:
1409 {
1410 GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping");
1411 /* fall through */
1412 }
1413
1414 out_error:
1415 {
1416 gst_hls_media_unref (media);
1417 return NULL;
1418 }
1419 }
1420
1421 static GstHLSVariantStream *
gst_hls_variant_stream_new(void)1422 gst_hls_variant_stream_new (void)
1423 {
1424 GstHLSVariantStream *stream;
1425
1426 stream = g_new0 (GstHLSVariantStream, 1);
1427 stream->m3u8 = gst_m3u8_new ();
1428 stream->refcount = 1;
1429 return stream;
1430 }
1431
1432 GstHLSVariantStream *
gst_hls_variant_stream_ref(GstHLSVariantStream * stream)1433 gst_hls_variant_stream_ref (GstHLSVariantStream * stream)
1434 {
1435 g_atomic_int_inc (&stream->refcount);
1436 return stream;
1437 }
1438
1439 void
gst_hls_variant_stream_unref(GstHLSVariantStream * stream)1440 gst_hls_variant_stream_unref (GstHLSVariantStream * stream)
1441 {
1442 if (g_atomic_int_dec_and_test (&stream->refcount)) {
1443 gint i;
1444
1445 g_free (stream->name);
1446 g_free (stream->uri);
1447 g_free (stream->codecs);
1448 gst_m3u8_unref (stream->m3u8);
1449 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1450 g_free (stream->media_groups[i]);
1451 g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref);
1452 }
1453 g_free (stream);
1454 }
1455 }
1456
1457 static GstHLSVariantStream *
find_variant_stream_by_name(GList * list,const gchar * name)1458 find_variant_stream_by_name (GList * list, const gchar * name)
1459 {
1460 for (; list != NULL; list = list->next) {
1461 GstHLSVariantStream *variant_stream = list->data;
1462
1463 if (variant_stream->name != NULL && !strcmp (variant_stream->name, name))
1464 return variant_stream;
1465 }
1466 return NULL;
1467 }
1468
1469 static GstHLSVariantStream *
find_variant_stream_by_uri(GList * list,const gchar * uri)1470 find_variant_stream_by_uri (GList * list, const gchar * uri)
1471 {
1472 for (; list != NULL; list = list->next) {
1473 GstHLSVariantStream *variant_stream = list->data;
1474
1475 if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri))
1476 return variant_stream;
1477 }
1478 return NULL;
1479 }
1480
1481 static GstHLSMasterPlaylist *
gst_hls_master_playlist_new(void)1482 gst_hls_master_playlist_new (void)
1483 {
1484 GstHLSMasterPlaylist *playlist;
1485
1486 playlist = g_new0 (GstHLSMasterPlaylist, 1);
1487 playlist->refcount = 1;
1488 playlist->is_simple = FALSE;
1489
1490 return playlist;
1491 }
1492
1493 void
gst_hls_master_playlist_unref(GstHLSMasterPlaylist * playlist)1494 gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist)
1495 {
1496 if (g_atomic_int_dec_and_test (&playlist->refcount)) {
1497 g_list_free_full (playlist->variants,
1498 (GDestroyNotify) gst_hls_variant_stream_unref);
1499 g_list_free_full (playlist->iframe_variants,
1500 (GDestroyNotify) gst_hls_variant_stream_unref);
1501 if (playlist->default_variant)
1502 gst_hls_variant_stream_unref (playlist->default_variant);
1503 g_free (playlist->last_data);
1504 g_free (playlist);
1505 }
1506 }
1507
1508 static gint
hls_media_name_compare_func(gconstpointer media,gconstpointer name)1509 hls_media_name_compare_func (gconstpointer media, gconstpointer name)
1510 {
1511 return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name);
1512 }
1513
1514 /* Takes ownership of @data */
1515 GstHLSMasterPlaylist *
gst_hls_master_playlist_new_from_data(gchar * data,const gchar * base_uri)1516 gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
1517 {
1518 GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, };
1519 GstHLSMasterPlaylist *playlist;
1520 GstHLSVariantStream *pending_stream;
1521 gchar *end, *free_data = data;
1522 gint val, i;
1523 GList *l;
1524
1525 if (!g_str_has_prefix (data, "#EXTM3U")) {
1526 GST_WARNING ("Data doesn't start with #EXTM3U");
1527 g_free (free_data);
1528 return NULL;
1529 }
1530
1531 playlist = gst_hls_master_playlist_new ();
1532
1533 /* store data before we modify it for parsing */
1534 playlist->last_data = g_strdup (data);
1535
1536 GST_TRACE ("data:\n%s", data);
1537
1538 if (strstr (data, "\n#EXTINF:") != NULL) {
1539 GST_INFO ("This is a simple media playlist, not a master playlist");
1540
1541 pending_stream = gst_hls_variant_stream_new ();
1542 pending_stream->name = g_strdup (base_uri);
1543 pending_stream->uri = g_strdup (base_uri);
1544 gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri);
1545 playlist->variants = g_list_append (playlist->variants, pending_stream);
1546 playlist->default_variant = gst_hls_variant_stream_ref (pending_stream);
1547 playlist->is_simple = TRUE;
1548
1549 if (!gst_m3u8_update (pending_stream->m3u8, data)) {
1550 GST_WARNING ("Failed to parse media playlist");
1551 gst_hls_master_playlist_unref (playlist);
1552 playlist = NULL;
1553 }
1554 return playlist;
1555 }
1556
1557 pending_stream = NULL;
1558 data += 7;
1559 while (TRUE) {
1560 gchar *r;
1561
1562 end = g_utf8_strchr (data, -1, '\n');
1563 if (end)
1564 *end = '\0';
1565
1566 r = g_utf8_strchr (data, -1, '\r');
1567 if (r)
1568 *r = '\0';
1569
1570 if (data[0] != '#' && data[0] != '\0') {
1571 gchar *name, *uri;
1572
1573 if (pending_stream == NULL) {
1574 GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data);
1575 goto next_line;
1576 }
1577
1578 name = data;
1579 uri = uri_join (base_uri, name);
1580 if (uri == NULL)
1581 goto next_line;
1582
1583 pending_stream->name = g_strdup (name);
1584 pending_stream->uri = uri;
1585
1586 if (find_variant_stream_by_name (playlist->variants, name)
1587 || find_variant_stream_by_uri (playlist->variants, uri)) {
1588 GST_DEBUG ("Already have a list with this name or URI: %s", name);
1589 gst_hls_variant_stream_unref (pending_stream);
1590 } else {
1591 GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri);
1592 gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name);
1593 playlist->variants = g_list_append (playlist->variants, pending_stream);
1594 /* use first stream in the playlist as default */
1595 if (playlist->default_variant == NULL) {
1596 playlist->default_variant =
1597 gst_hls_variant_stream_ref (pending_stream);
1598 }
1599 }
1600 pending_stream = NULL;
1601 } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
1602 if (int_from_string (data + 15, &data, &val))
1603 playlist->version = val;
1604 } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
1605 g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
1606 GstHLSVariantStream *stream;
1607 gchar *v, *a;
1608
1609 stream = gst_hls_variant_stream_new ();
1610 stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
1611 data += stream->iframe ? 26 : 18;
1612 while (data && parse_attributes (&data, &a, &v)) {
1613 if (g_str_equal (a, "BANDWIDTH")) {
1614 if (!stream->bandwidth) {
1615 if (!int_from_string (v, NULL, &stream->bandwidth))
1616 GST_WARNING ("Error while reading BANDWIDTH");
1617 }
1618 } else if (g_str_equal (a, "AVERAGE-BANDWIDTH")) {
1619 GST_DEBUG
1620 ("AVERAGE-BANDWIDTH attribute available. Using it as stream bandwidth");
1621 if (!int_from_string (v, NULL, &stream->bandwidth))
1622 GST_WARNING ("Error while reading AVERAGE-BANDWIDTH");
1623 } else if (g_str_equal (a, "PROGRAM-ID")) {
1624 if (!int_from_string (v, NULL, &stream->program_id))
1625 GST_WARNING ("Error while reading PROGRAM-ID");
1626 } else if (g_str_equal (a, "CODECS")) {
1627 g_free (stream->codecs);
1628 stream->codecs = g_strdup (v);
1629 } else if (g_str_equal (a, "RESOLUTION")) {
1630 if (!int_from_string (v, &v, &stream->width))
1631 GST_WARNING ("Error while reading RESOLUTION width");
1632 if (!v || *v != 'x') {
1633 GST_WARNING ("Missing height");
1634 } else {
1635 v = g_utf8_next_char (v);
1636 if (!int_from_string (v, NULL, &stream->height))
1637 GST_WARNING ("Error while reading RESOLUTION height");
1638 }
1639 } else if (stream->iframe && g_str_equal (a, "URI")) {
1640 stream->uri = uri_join (base_uri, v);
1641 if (stream->uri != NULL) {
1642 stream->name = g_strdup (stream->uri);
1643 gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name);
1644 } else {
1645 gst_hls_variant_stream_unref (stream);
1646 }
1647 } else if (g_str_equal (a, "AUDIO")) {
1648 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]);
1649 stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v);
1650 } else if (g_str_equal (a, "SUBTITLES")) {
1651 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]);
1652 stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] =
1653 gst_m3u8_unquote (v);
1654 } else if (g_str_equal (a, "VIDEO")) {
1655 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]);
1656 stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v);
1657 } else if (g_str_equal (a, "CLOSED-CAPTIONS")) {
1658 /* closed captions will be embedded inside the video stream, ignore */
1659 }
1660 }
1661
1662 if (stream->iframe) {
1663 if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) {
1664 GST_DEBUG ("Already have a list with this URI");
1665 gst_hls_variant_stream_unref (stream);
1666 } else {
1667 playlist->iframe_variants =
1668 g_list_append (playlist->iframe_variants, stream);
1669 }
1670 } else {
1671 if (pending_stream != NULL) {
1672 GST_WARNING ("variant stream without uri, dropping");
1673 gst_hls_variant_stream_unref (pending_stream);
1674 }
1675 pending_stream = stream;
1676 }
1677 } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) {
1678 GstHLSMedia *media;
1679 GList *list;
1680
1681 media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri);
1682
1683 if (media == NULL)
1684 goto next_line;
1685
1686 if (media_groups[media->mtype] == NULL) {
1687 media_groups[media->mtype] =
1688 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1689 }
1690
1691 list = g_hash_table_lookup (media_groups[media->mtype], media->group_id);
1692
1693 /* make sure there isn't already a media with the same name */
1694 if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) {
1695 g_hash_table_replace (media_groups[media->mtype],
1696 g_strdup (media->group_id), g_list_append (list, media));
1697 GST_INFO ("Added media %s to group %s", media->name, media->group_id);
1698 } else {
1699 GST_WARNING (" media with name '%s' already exists in group '%s'!",
1700 media->name, media->group_id);
1701 gst_hls_media_unref (media);
1702 }
1703 } else if (*data != '\0') {
1704 GST_LOG ("Ignored line: %s", data);
1705 }
1706
1707 next_line:
1708 if (!end)
1709 break;
1710 data = g_utf8_next_char (end); /* skip \n */
1711 }
1712
1713 if (pending_stream != NULL) {
1714 GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping");
1715 gst_hls_variant_stream_unref (pending_stream);
1716 }
1717
1718 g_free (free_data);
1719
1720 /* Add alternative renditions media to variant streams */
1721 for (l = playlist->variants; l != NULL; l = l->next) {
1722 GstHLSVariantStream *stream = l->data;
1723 GList *mlist;
1724
1725 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1726 if (stream->media_groups[i] != NULL && media_groups[i] != NULL) {
1727 GST_INFO ("Adding %s group '%s' to stream '%s'",
1728 GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name);
1729
1730 mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]);
1731
1732 if (mlist == NULL)
1733 GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]);
1734
1735 while (mlist != NULL) {
1736 GstHLSMedia *media = mlist->data;
1737
1738 GST_DEBUG (" %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i),
1739 media->name, media->uri);
1740
1741 stream->media[i] =
1742 g_list_append (stream->media[i], gst_hls_media_ref (media));
1743 mlist = mlist->next;
1744 }
1745 }
1746 }
1747 }
1748
1749 /* clean up our temporary alternative rendition groups hash tables */
1750 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1751 if (media_groups[i] != NULL) {
1752 GList *groups, *mlist;
1753
1754 groups = g_hash_table_get_keys (media_groups[i]);
1755 for (l = groups; l != NULL; l = l->next) {
1756 mlist = g_hash_table_lookup (media_groups[i], l->data);
1757 g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref);
1758 }
1759 g_list_free (groups);
1760 g_hash_table_unref (media_groups[i]);
1761 }
1762 }
1763
1764 if (playlist->variants == NULL) {
1765 GST_WARNING ("Master playlist without any media playlists!");
1766 gst_hls_master_playlist_unref (playlist);
1767 return NULL;
1768 }
1769
1770 /* reorder variants by bitrate */
1771 playlist->variants =
1772 g_list_sort (playlist->variants,
1773 (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1774
1775 playlist->iframe_variants =
1776 g_list_sort (playlist->iframe_variants,
1777 (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1778
1779 /* FIXME: restore old current_variant after master playlist update
1780 * (move into code that does that update) */
1781 #if 0
1782 {
1783 gchar *top_variant_uri = NULL;
1784 gboolean iframe = FALSE;
1785
1786 if (!self->current_variant) {
1787 top_variant_uri = GST_M3U8 (self->lists->data)->uri;
1788 } else {
1789 top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
1790 iframe = GST_M3U8 (self->current_variant->data)->iframe;
1791 }
1792
1793 /* here we sorted the lists */
1794
1795 if (iframe)
1796 playlist->current_variant =
1797 find_variant_stream_by_uri (playlist->iframe_variants,
1798 top_variant_uri);
1799 else
1800 playlist->current_variant =
1801 find_variant_stream_by_uri (playlist->variants, top_variant_uri);
1802 }
1803 #endif
1804
1805 GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams",
1806 g_list_length (playlist->variants),
1807 g_list_length (playlist->iframe_variants));
1808
1809
1810 return playlist;
1811 }
1812
1813 gboolean
gst_hls_variant_stream_is_live(GstHLSVariantStream * variant)1814 gst_hls_variant_stream_is_live (GstHLSVariantStream * variant)
1815 {
1816 gboolean is_live;
1817
1818 g_return_val_if_fail (variant != NULL, FALSE);
1819
1820 is_live = gst_m3u8_is_live (variant->m3u8);
1821
1822 return is_live;
1823 }
1824
1825 static gint
compare_media(const GstHLSMedia * a,const GstHLSMedia * b)1826 compare_media (const GstHLSMedia * a, const GstHLSMedia * b)
1827 {
1828 return strcmp (a->name, b->name);
1829 }
1830
1831 GstHLSMedia *
gst_hls_variant_find_matching_media(GstHLSVariantStream * stream,GstHLSMedia * media)1832 gst_hls_variant_find_matching_media (GstHLSVariantStream * stream,
1833 GstHLSMedia * media)
1834 {
1835 GList *mlist = stream->media[media->mtype];
1836 GList *match;
1837
1838 if (mlist == NULL)
1839 return NULL;
1840
1841 match = g_list_find_custom (mlist, media, (GCompareFunc) compare_media);
1842 if (match == NULL)
1843 return NULL;
1844
1845 return match->data;
1846 }
1847
1848 GstHLSVariantStream *
gst_hls_master_playlist_get_variant_for_bitrate(GstHLSMasterPlaylist * playlist,GstHLSVariantStream * current_variant,guint bitrate)1849 gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
1850 playlist, GstHLSVariantStream * current_variant, guint bitrate)
1851 {
1852 GstHLSVariantStream *variant = current_variant;
1853 GList *l;
1854
1855 /* variant lists are sorted low to high, so iterate from highest to lowest */
1856 if (current_variant == NULL || !current_variant->iframe)
1857 l = g_list_last (playlist->variants);
1858 else
1859 l = g_list_last (playlist->iframe_variants);
1860
1861 while (l != NULL) {
1862 variant = l->data;
1863 if (variant->bandwidth <= bitrate)
1864 break;
1865 l = l->prev;
1866 }
1867
1868 return variant;
1869 }
1870
1871 GstHLSVariantStream *
gst_hls_master_playlist_get_matching_variant(GstHLSMasterPlaylist * playlist,GstHLSVariantStream * current_variant)1872 gst_hls_master_playlist_get_matching_variant (GstHLSMasterPlaylist * playlist,
1873 GstHLSVariantStream * current_variant)
1874 {
1875 if (current_variant->iframe) {
1876 return find_variant_stream_by_uri (playlist->iframe_variants,
1877 current_variant->uri);
1878 }
1879
1880 return find_variant_stream_by_uri (playlist->variants, current_variant->uri);
1881 }
1882