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