• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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