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