2 * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
27 #include <gst/glib-compat-private.h>
28 #include "gstfragmented.h"
31 #define GST_CAT_DEFAULT fragmented_debug
33 static GstM3U8 *gst_m3u8_new (void);
34 static void gst_m3u8_free (GstM3U8 * m3u8);
35 static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
37 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
38 gchar * title, GstClockTime duration, guint sequence);
39 static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
40 static GstM3U8Key *gst_m3u8_key_new (GstM3U8EncryptionMethod method,
41 gchar * uri, guint8 *iv, guint sequence);
42 static void gst_m3u8_key_free (GstM3U8Key * self);
49 m3u8 = g_new0 (GstM3U8, 1);
55 gst_m3u8_set_uri (GstM3U8 * self, gchar * uri)
57 g_return_if_fail (self != NULL);
65 gst_m3u8_free (GstM3U8 * self)
67 g_return_if_fail (self != NULL);
70 g_free (self->allowcache);
71 g_free (self->codecs);
72 g_free (self->cipher_ctx);
74 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
75 g_list_free (self->files);
77 g_list_foreach (self->keys, (GFunc) gst_m3u8_key_free, NULL);
78 g_list_free (self->keys);
80 g_free (self->last_data);
81 g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
82 g_list_free (self->lists);
87 static GstM3U8MediaFile *
88 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
91 GstM3U8MediaFile *file;
93 file = g_new0 (GstM3U8MediaFile, 1);
96 file->duration = duration;
97 file->sequence = sequence;
103 gst_m3u8_media_file_free (GstM3U8MediaFile * self)
105 g_return_if_fail (self != NULL);
107 g_free (self->title);
113 gst_m3u8_key_new (GstM3U8EncryptionMethod method, gchar * uri,
114 guint8 *iv, guint sequence)
118 key = g_new0 (GstM3U8Key, 1);
119 key->method = method;
122 key->sequence = sequence;
129 gst_m3u8_key_free (GstM3U8Key * self)
131 g_return_if_fail (self != NULL);
140 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
145 g_return_val_if_fail (ptr != NULL, FALSE);
146 g_return_val_if_fail (val != NULL, FALSE);
149 ret = strtol (ptr, &end, 10);
150 if ((errno == ERANGE && (ret == LONG_MAX || ret == LONG_MIN))
151 || (errno != 0 && ret == 0)) {
152 GST_WARNING ("%s", g_strerror (errno));
156 if (ret > G_MAXINT) {
157 GST_WARNING ("%s", g_strerror (ERANGE));
170 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
175 g_return_val_if_fail (ptr != NULL, FALSE);
176 g_return_val_if_fail (val != NULL, FALSE);
179 ret = strtod (ptr, &end);
180 if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
181 || (errno != 0 && ret == 0)) {
182 GST_WARNING ("%s", g_strerror (errno));
186 if (!isfinite (ret)) {
187 GST_WARNING ("%s", g_strerror (ERANGE));
200 iv_from_string (gchar * ptr, gchar ** endptr, guint8 * val)
205 g_return_val_if_fail (ptr != NULL, FALSE);
206 g_return_val_if_fail (val != NULL, FALSE);
208 if (*ptr == '0' && (*(ptr + 1) == 'x' || *(ptr + 1) == 'X')) {
209 ptr = ptr + 2; /* skip 0x or 0X */
212 for (idx = 0; idx < GST_M3U8_IV_LEN; idx++) {
216 *(val + idx) = hex * 16;
230 iv_from_uint (guint uint, guint8 * val)
234 g_return_val_if_fail (val != NULL, FALSE);
236 val = val + GST_M3U8_IV_LEN - 1;
237 for (idx = 0; idx < sizeof(guint); idx++) {
238 *val-- = (guint8)uint;
246 make_valid_uri (gchar *prefix, gchar *suffix, gchar **uri)
251 GST_WARNING ("uri prefix not set, can't build a valid uri");
254 slash = g_utf8_strrchr (prefix, -1, '/');
256 GST_WARNING ("Can't build a valid uri");
261 *uri = g_strdup_printf ("%s/%s", prefix, suffix);
268 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
272 g_return_val_if_fail (ptr != NULL, FALSE);
273 g_return_val_if_fail (*ptr != NULL, FALSE);
274 g_return_val_if_fail (a != NULL, FALSE);
275 g_return_val_if_fail (v != NULL, FALSE);
277 /* [attribute=value,]* */
280 end = p = g_utf8_strchr (*ptr, -1, ',');
283 end = g_utf8_next_char (end);
284 } while (end && *end == ' ');
288 *v = p = g_utf8_strchr (*ptr, -1, '=');
290 *v = g_utf8_next_char (*v);
293 GST_WARNING ("missing = after attribute");
302 _m3u8_compare_uri (GstM3U8 * a, gchar * uri)
304 g_return_val_if_fail (a != NULL, 0);
305 g_return_val_if_fail (uri != NULL, 0);
307 return g_strcmp0 (a->uri, uri);
311 gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
313 return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
317 * @data: a m3u8 playlist text data, taking ownership
320 gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
323 GstClockTime duration;
325 // gboolean discontinuity;
328 g_return_val_if_fail (self != NULL, FALSE);
329 g_return_val_if_fail (data != NULL, FALSE);
330 g_return_val_if_fail (updated != NULL, FALSE);
334 /* check if the data changed since last update */
335 if (self->last_data && g_str_equal (self->last_data, data)) {
336 GST_DEBUG ("Playlist is the same as previous one");
342 if (!g_str_has_prefix (data, "#EXTM3U")) {
343 GST_WARNING ("Data doesn't start with #EXTM3U");
349 g_free (self->last_data);
350 self->last_data = data;
353 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
354 g_list_free (self->files);
359 g_list_foreach (self->keys, (GFunc) gst_m3u8_key_free, NULL);
360 g_list_free (self->keys);
369 end = g_utf8_strchr (data, -1, '\n');
373 if (data[0] != '#') {
376 if (duration <= 0 && list == NULL) {
377 GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
381 if (!gst_uri_is_valid (data)) {
382 if (!make_valid_uri(self->uri, data, &data))
385 data = g_strdup (data);
388 r = g_utf8_strchr (data, -1, '\r');
393 if (g_list_find_custom (self->lists, data,
394 (GCompareFunc) _m3u8_compare_uri)) {
395 GST_DEBUG ("Already have a list with this URI");
396 gst_m3u8_free (list);
399 gst_m3u8_set_uri (list, data);
400 self->lists = g_list_append (self->lists, list);
404 GstM3U8MediaFile *file;
406 gst_m3u8_media_file_new (data, title, duration,
407 self->mediasequence++);
410 self->files = g_list_append (self->files, file);
413 } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
414 self->endlist = TRUE;
415 } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
416 if (int_from_string (data + 15, &data, &val))
418 } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) {
422 GST_WARNING ("Found a list without a uri..., dropping");
423 gst_m3u8_free (list);
426 list = gst_m3u8_new ();
428 while (data && parse_attributes (&data, &a, &v)) {
429 if (g_str_equal (a, "BANDWIDTH")) {
430 if (!int_from_string (v, NULL, &list->bandwidth))
431 GST_WARNING ("Error while reading BANDWIDTH");
432 } else if (g_str_equal (a, "PROGRAM-ID")) {
433 if (!int_from_string (v, NULL, &list->program_id))
434 GST_WARNING ("Error while reading PROGRAM-ID");
435 } else if (g_str_equal (a, "CODECS")) {
436 g_free (list->codecs);
437 list->codecs = g_strdup (v);
438 } else if (g_str_equal (a, "RESOLUTION")) {
439 if (!int_from_string (v, &v, &list->width))
440 GST_WARNING ("Error while reading RESOLUTION width");
441 if (!v || *v != '=') {
442 GST_WARNING ("Missing height");
444 v = g_utf8_next_char (v);
445 if (!int_from_string (v, NULL, &list->height))
446 GST_WARNING ("Error while reading RESOLUTION height");
450 } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
451 if (int_from_string (data + 22, &data, &val))
452 self->targetduration = val * GST_SECOND;
453 } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
454 if (int_from_string (data + 22, &data, &val))
455 self->mediasequence = val;
456 } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
457 /* discontinuity = TRUE; */
458 } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
459 /* <YYYY-MM-DDThh:mm:ssZ> */
460 GST_DEBUG ("FIXME parse date");
461 } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
462 g_free (self->allowcache);
463 self->allowcache = g_strdup (data + 19);
464 } else if (g_str_has_prefix (data, "#EXTINF:")) {
466 if (!double_from_string (data + 8, &data, &fval)) {
467 GST_WARNING ("Can't read EXTINF duration");
470 duration = fval * (gdouble) GST_SECOND;
471 if (duration > self->targetduration)
472 GST_WARNING ("EXTINF duration > TARGETDURATION");
473 if (!data || *data != ',')
475 data = g_utf8_next_char (data);
478 title = g_strdup (data);
480 } else if (g_str_has_prefix (data, "#EXT-X-KEY:")) {
483 GstM3U8EncryptionMethod encryption;
488 encryption = GST_M3U8_ENCRYPTED_NONE;
491 while (data && parse_attributes (&data, &a, &v)) {
492 if (g_str_equal (a, "METHOD")) {
493 if (g_str_equal (v, "NONE")) {
494 encryption = GST_M3U8_ENCRYPTED_NONE;
495 } else if (g_str_equal (v, "AES-128")) {
496 encryption = GST_M3U8_ENCRYPTED_AES_128;
498 GST_WARNING ("Unsuppported encryption method..., skipping");
501 } else if (g_str_equal (a, "URI")) {
505 v = v + 1; /* skip first double quote in uri */
507 dq = g_utf8_strrchr (v, -1, '"');
510 if (!gst_uri_is_valid (v)) {
511 if (!make_valid_uri(self->uri, v, &key_uri))
514 key_uri = g_strdup(v);
516 r = g_utf8_strchr (v, -1, '\r');
519 } else if (g_str_equal (a, "IV")) {
520 iv = g_malloc0 (GST_M3U8_IV_LEN);
521 if (!iv_from_string (data, &data, iv)) {
522 GST_WARNING ("Can't read IV");
527 if ((encryption != GST_M3U8_ENCRYPTED_NONE) && (key_uri != NULL)) {
529 iv = g_malloc0 (GST_M3U8_IV_LEN);
530 if (!iv_from_uint (self->mediasequence, iv)) {
531 GST_WARNING ("Can't convert IV from sequence");
538 if (encryption != GST_M3U8_ENCRYPTED_NONE) {
539 GST_WARNING ("Key uri not set");
543 key = gst_m3u8_key_new (encryption, key_uri, iv,
544 self->mediasequence);
545 self->keys = g_list_prepend (self->keys, key);
547 GST_LOG ("Ignored line: %s", data);
553 data = g_utf8_next_char (end); /* skip \n */
556 /* redorder playlists by bitrate */
558 gchar *top_variant_uri = NULL;
560 if (!self->current_variant)
561 top_variant_uri = GST_M3U8 (self->lists->data)->uri;
563 top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
566 g_list_sort (self->lists,
567 (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
569 self->current_variant = g_list_find_custom (self->lists, top_variant_uri,
570 (GCompareFunc) _m3u8_compare_uri);
577 gst_m3u8_client_new (const gchar * uri)
579 GstM3U8Client *client;
581 g_return_val_if_fail (uri != NULL, NULL);
583 client = g_new0 (GstM3U8Client, 1);
584 client->main = gst_m3u8_new ();
585 client->current = NULL;
586 client->sequence = -1;
587 client->update_failed_count = 0;
588 client->lock = g_mutex_new ();
589 gst_m3u8_set_uri (client->main, g_strdup (uri));
595 gst_m3u8_client_free (GstM3U8Client * self)
597 g_return_if_fail (self != NULL);
599 gst_m3u8_free (self->main);
600 g_mutex_free (self->lock);
605 gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
607 g_return_if_fail (self != NULL);
609 GST_M3U8_CLIENT_LOCK (self);
610 if (m3u8 != self->current) {
611 self->current = m3u8;
612 self->update_failed_count = 0;
614 GST_M3U8_CLIENT_UNLOCK (self);
618 gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
621 gboolean updated = FALSE;
622 gboolean ret = FALSE;
624 g_return_val_if_fail (self != NULL, FALSE);
626 GST_M3U8_CLIENT_LOCK (self);
627 m3u8 = self->current ? self->current : self->main;
629 if (!gst_m3u8_update (m3u8, data, &updated))
633 self->update_failed_count++;
637 /* select the first playlist, for now */
638 if (!self->current) {
639 if (self->main->lists) {
640 self->current = self->main->current_variant->data;
642 self->current = self->main;
646 if (m3u8->files && self->sequence == -1) {
648 GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
649 GST_DEBUG ("Setting first sequence at %d", self->sequence);
654 GST_M3U8_CLIENT_UNLOCK (self);
659 _find_next (GstM3U8MediaFile * file, GstM3U8Client * client)
661 GST_DEBUG ("Found fragment %d", file->sequence);
662 if (file->sequence >= client->sequence)
668 _find_key (GstM3U8Key * key, GstM3U8Client * client)
670 if (key->sequence <= client->sequence)
676 gst_m3u8_client_get_current_position (GstM3U8Client * client,
677 GstClockTime * timestamp)
682 l = g_list_find_custom (client->current->files, client,
683 (GCompareFunc) _find_next);
686 for (walk = client->current->files; walk; walk = walk->next) {
689 *timestamp += GST_M3U8_MEDIA_FILE (walk->data)->duration;
694 gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
695 gboolean * discontinuity, const gchar ** uri, GstClockTime * duration,
696 GstClockTime * timestamp, GstM3U8Key ** key)
699 GstM3U8MediaFile *file;
701 g_return_val_if_fail (client != NULL, FALSE);
702 g_return_val_if_fail (client->current != NULL, FALSE);
703 g_return_val_if_fail (discontinuity != NULL, FALSE);
705 GST_M3U8_CLIENT_LOCK (client);
706 GST_DEBUG ("Looking for fragment %d", client->sequence);
707 l = g_list_find_custom (client->current->files, client,
708 (GCompareFunc) _find_next);
710 GST_M3U8_CLIENT_UNLOCK (client);
714 gst_m3u8_client_get_current_position (client, timestamp);
716 file = GST_M3U8_MEDIA_FILE (l->data);
719 if (client->current->keys) {
720 l = g_list_find_custom (client->current->keys, client,
721 (GCompareFunc) _find_key);
722 if (l && (GST_M3U8_KEY (l->data)->method != GST_M3U8_ENCRYPTED_NONE))
723 *key = GST_M3U8_KEY (l->data);
726 *discontinuity = client->sequence != file->sequence;
727 client->sequence = file->sequence + 1;
730 *duration = file->duration;
732 GST_M3U8_CLIENT_UNLOCK (client);
737 _sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
739 *duration += self->duration;
743 gst_m3u8_client_get_duration (GstM3U8Client * client)
745 GstClockTime duration = 0;
747 g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
749 GST_M3U8_CLIENT_LOCK (client);
750 /* We can only get the duration for on-demand streams */
751 if (!client->current->endlist) {
752 GST_M3U8_CLIENT_UNLOCK (client);
753 return GST_CLOCK_TIME_NONE;
756 g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
757 GST_M3U8_CLIENT_UNLOCK (client);
762 gst_m3u8_client_get_target_duration (GstM3U8Client * client)
764 GstClockTime duration = 0;
766 g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
768 GST_M3U8_CLIENT_LOCK (client);
769 duration = client->current->targetduration;
770 GST_M3U8_CLIENT_UNLOCK (client);
775 gst_m3u8_client_get_uri (GstM3U8Client * client)
779 g_return_val_if_fail (client != NULL, NULL);
781 GST_M3U8_CLIENT_LOCK (client);
782 uri = client->main->uri;
783 GST_M3U8_CLIENT_UNLOCK (client);
788 gst_m3u8_client_get_current_uri (GstM3U8Client * client)
792 g_return_val_if_fail (client != NULL, NULL);
794 GST_M3U8_CLIENT_LOCK (client);
795 uri = client->current->uri;
796 GST_M3U8_CLIENT_UNLOCK (client);
801 gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
805 g_return_val_if_fail (client != NULL, FALSE);
807 GST_M3U8_CLIENT_LOCK (client);
808 ret = (client->main->lists != NULL);
809 GST_M3U8_CLIENT_UNLOCK (client);
814 gst_m3u8_client_is_live (GstM3U8Client * client)
818 g_return_val_if_fail (client != NULL, FALSE);
820 GST_M3U8_CLIENT_LOCK (client);
821 if (!client->current || client->current->endlist)
825 GST_M3U8_CLIENT_UNLOCK (client);
830 gst_m3u8_client_get_playlist_for_bitrate (GstM3U8Client * client, guint bitrate)
832 GList *list, *current_variant;
834 GST_M3U8_CLIENT_LOCK (client);
835 current_variant = client->main->current_variant;
837 /* Go to the highest possible bandwidth allowed */
838 while (GST_M3U8 (current_variant->data)->bandwidth < bitrate) {
839 list = g_list_next (current_variant);
842 current_variant = list;
845 while (GST_M3U8 (current_variant->data)->bandwidth > bitrate) {
846 list = g_list_previous (current_variant);
849 current_variant = list;
851 GST_M3U8_CLIENT_UNLOCK (client);
853 return current_variant;
857 gst_m3u8_client_decrypt_init (GstM3U8Client * client, GstM3U8Key * key)
859 if (!client->current->cipher_ctx)
860 client->current->cipher_ctx = g_malloc0 (sizeof(EVP_CIPHER_CTX));
862 EVP_CIPHER_CTX_init (client->current->cipher_ctx);
863 if (key->method == GST_M3U8_ENCRYPTED_AES_128) {
864 return EVP_DecryptInit_ex (client->current->cipher_ctx, EVP_aes_128_cbc(),
865 NULL, key->data, key->iv);
872 gst_m3u8_client_decrypt_update (GstM3U8Client * client, guint8 * out_data,
873 gint * out_size, guint8 * in_data, gint in_size)
875 g_return_val_if_fail (client->current->cipher_ctx != NULL, FALSE);
877 return EVP_DecryptUpdate (client->current->cipher_ctx, out_data, out_size,