2 * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
3 * Copyright (C) 2015 Tim-Philipp Müller <tim@centricular.com>
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.
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.
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.
32 #define GST_CAT_DEFAULT hls_debug
33 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
34 #define DEFAULT_RESOLUTION_LIMIT -1
35 #define DEFAULT_BANDWIDTH_LIMIT -1
38 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
39 gchar * title, GstClockTime duration, guint sequence);
40 static gchar *uri_join (const gchar * uri, const gchar * path);
47 m3u8 = g_new0 (GstM3U8, 1);
49 m3u8->current_file = NULL;
50 m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
52 m3u8->sequence_position = 0;
53 m3u8->highest_sequence_number = -1;
54 m3u8->duration = GST_CLOCK_TIME_NONE;
55 #ifdef TIZEN_FEATURE_AD
56 m3u8->ad_info = g_new0 (GstM3U8AdInfo, 1);
59 g_mutex_init (&m3u8->lock);
65 /* call with M3U8_LOCK held */
67 gst_m3u8_take_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
69 g_return_if_fail (self != NULL);
71 if (self->uri != uri) {
75 if (self->base_uri != base_uri) {
76 g_free (self->base_uri);
77 self->base_uri = base_uri;
79 if (self->name != name) {
86 gst_m3u8_set_uri (GstM3U8 * m3u8, const gchar * uri, const gchar * base_uri,
90 gst_m3u8_take_uri (m3u8, g_strdup (uri), g_strdup (base_uri),
92 GST_M3U8_UNLOCK (m3u8);
95 #ifdef TIZEN_FEATURE_AD
97 gst_m3u8_cue_info_new (GstClockTime start_time, GstClockTime duration)
101 ad = g_new0 (GstM3U8Cue, 1);
102 ad->start_time = start_time;
104 ad->duration = duration;
110 gst_m3u8_cue_cont_free (GstM3U8CueOutCont * self)
112 g_return_if_fail (self != NULL);
113 g_free (self->cont_data);
118 gst_m3u8_ref (GstM3U8 * m3u8)
120 g_assert (m3u8 != NULL && m3u8->ref_count > 0);
122 g_atomic_int_add (&m3u8->ref_count, 1);
127 gst_m3u8_unref (GstM3U8 * self)
129 g_return_if_fail (self != NULL && self->ref_count > 0);
131 if (g_atomic_int_dec_and_test (&self->ref_count)) {
133 g_free (self->base_uri);
136 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL);
137 g_list_free (self->files);
139 #ifdef TIZEN_FEATURE_AD
141 g_list_free (self->ad_info->cue);
142 g_list_free_full(self->ad_info->cue_cont, (GFunc) gst_m3u8_cue_cont_free);
143 g_free (self->ad_info);
147 g_free (self->last_data);
148 g_mutex_clear (&self->lock);
153 static GstM3U8MediaFile *
154 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
157 GstM3U8MediaFile *file;
159 file = g_new0 (GstM3U8MediaFile, 1);
162 file->duration = duration;
163 file->sequence = sequence;
170 gst_m3u8_media_file_ref (GstM3U8MediaFile * mfile)
172 g_assert (mfile != NULL && mfile->ref_count > 0);
174 g_atomic_int_add (&mfile->ref_count, 1);
179 gst_m3u8_media_file_unref (GstM3U8MediaFile * self)
181 g_return_if_fail (self != NULL && self->ref_count > 0);
183 if (g_atomic_int_dec_and_test (&self->ref_count)) {
184 g_free (self->title);
192 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
197 g_return_val_if_fail (ptr != NULL, FALSE);
198 g_return_val_if_fail (val != NULL, FALSE);
201 ret = g_ascii_strtoll (ptr, &end, 10);
202 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
203 || (errno != 0 && ret == 0)) {
204 GST_WARNING ("%s", g_strerror (errno));
208 if (ret > G_MAXINT || ret < G_MININT) {
209 GST_WARNING ("%s", g_strerror (ERANGE));
222 int64_from_string (gchar * ptr, gchar ** endptr, gint64 * val)
227 g_return_val_if_fail (ptr != NULL, FALSE);
228 g_return_val_if_fail (val != NULL, FALSE);
231 ret = g_ascii_strtoll (ptr, &end, 10);
232 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
233 || (errno != 0 && ret == 0)) {
234 GST_WARNING ("%s", g_strerror (errno));
247 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
252 g_return_val_if_fail (ptr != NULL, FALSE);
253 g_return_val_if_fail (val != NULL, FALSE);
256 ret = g_ascii_strtod (ptr, &end);
257 if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
258 || (errno != 0 && ret == 0)) {
259 GST_WARNING ("%s", g_strerror (errno));
263 if (!isfinite (ret)) {
264 GST_WARNING ("%s", g_strerror (ERANGE));
271 *val = (gdouble) ret;
277 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
279 gchar *end = NULL, *p, *ve;
281 g_return_val_if_fail (ptr != NULL, FALSE);
282 g_return_val_if_fail (*ptr != NULL, FALSE);
283 g_return_val_if_fail (a != NULL, FALSE);
284 g_return_val_if_fail (v != NULL, FALSE);
286 /* [attribute=value,]* */
289 end = p = g_utf8_strchr (*ptr, -1, ',');
291 gchar *q = g_utf8_strchr (*ptr, -1, '"');
293 /* special case, such as CODECS="avc1.77.30, mp4a.40.2" */
294 q = g_utf8_next_char (q);
296 q = g_utf8_strchr (q, -1, '"');
299 end = p = g_utf8_strchr (q, -1, ',');
305 end = g_utf8_next_char (end);
306 } while (end && *end == ' ');
310 *v = p = g_utf8_strchr (*ptr, -1, '=');
313 *v = g_utf8_next_char (*v);
315 ve = g_utf8_next_char (*v);
317 ve = g_utf8_strchr (ve, -1, '"');
320 *v = g_utf8_next_char (*v);
323 GST_WARNING ("Cannot remove quotation marks from %s", *a);
327 GST_WARNING ("missing = after attribute");
336 gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b)
338 const GstHLSVariantStream *vs_a = (const GstHLSVariantStream *) a;
339 const GstHLSVariantStream *vs_b = (const GstHLSVariantStream *) b;
341 if (vs_a->bandwidth == vs_b->bandwidth)
342 return g_strcmp0 (vs_a->name, vs_b->name);
344 return vs_a->bandwidth - vs_b->bandwidth;
347 /* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
348 * the client SHOULD halt playback (6.3.4), which is what we do then. */
350 check_media_seqnums (GstM3U8 * self, GList * previous_files)
353 GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
355 g_return_val_if_fail (previous_files, FALSE);
358 /* Empty playlists are trivially consistent */
362 /* Find first case of higher/equal sequence number in new playlist.
363 * From there on we can linearly step ahead */
364 for (l = self->files; l; l = l->next) {
365 gboolean match = FALSE;
368 for (m = previous_files; m; m = m->next) {
371 if (f1->sequence >= f2->sequence) {
380 /* We must have seen at least one entry on each list */
381 g_assert (f1 != NULL);
382 g_assert (f2 != NULL);
385 /* No match, no sequence in the new playlist was higher than
386 * any in the old. This is bad! */
387 GST_ERROR ("Media sequence doesn't continue: last new %" G_GINT64_FORMAT
388 " < last old %" G_GINT64_FORMAT, f1->sequence, f2->sequence);
392 for (; l && m; l = l->next, m = m->next) {
396 if (f1->sequence == f2->sequence && !g_str_equal (f1->uri, f2->uri)) {
397 /* Same sequence, different URI. This is bad! */
398 GST_ERROR ("Media URIs inconsistent (sequence %" G_GINT64_FORMAT
399 "): had '%s', got '%s'", f1->sequence, f2->uri, f1->uri);
401 } else if (f1->sequence < f2->sequence) {
402 /* Not same sequence but by construction sequence must be higher in the
403 * new one. All good in that case, if it isn't then this means that
404 * sequence numbers are decreasing or files were inserted */
405 GST_ERROR ("Media sequences inconsistent: %" G_GINT64_FORMAT " < %"
406 G_GINT64_FORMAT ": URIs new '%s' old '%s'", f1->sequence,
407 f2->sequence, f1->uri, f2->uri);
412 /* All good if we're getting here */
416 /* If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
417 * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
418 * playlist in relation to the old. That is, same URIs get the same number
419 * and later URIs get higher numbers */
421 generate_media_seqnums (GstM3U8 * self, GList * previous_files)
424 GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
425 gint64 mediasequence;
427 g_return_if_fail (previous_files);
429 /* Find first case of same URI in new playlist.
430 * From there on we can linearly step ahead */
431 for (l = self->files; l; l = l->next) {
432 gboolean match = FALSE;
435 for (m = previous_files; m; m = m->next) {
438 if (g_str_equal (f1->uri, f2->uri)) {
449 /* Match, check that all following ones are matching too and continue
450 * sequence numbers from there on */
452 mediasequence = f2->sequence;
454 for (; l && m; l = l->next, m = m->next) {
458 f1->sequence = mediasequence;
461 if (!g_str_equal (f1->uri, f2->uri)) {
462 GST_WARNING ("Inconsistent URIs after playlist update: '%s' != '%s'",
467 /* No match, this means f2 is the last item in the previous playlist
468 * and we have to start our new playlist at that sequence */
469 mediasequence = f2->sequence + 1;
473 for (; l; l = l->next) {
476 f1->sequence = mediasequence;
482 * @data: a m3u8 playlist text data, taking ownership
485 gst_m3u8_update (GstM3U8 * self, gchar * data)
488 GstClockTime duration;
490 gboolean discontinuity = FALSE;
491 gchar *current_key = NULL;
492 gboolean have_iv = FALSE;
493 guint8 iv[16] = { 0, };
494 gint64 size = -1, offset = -1;
495 gint64 mediasequence;
496 GList *previous_files = NULL;
497 gboolean have_mediasequence = FALSE;
498 #ifdef TIZEN_FEATURE_AD
499 GstClockTime timestamp = 0;
502 g_return_val_if_fail (self != NULL, FALSE);
503 g_return_val_if_fail (data != NULL, FALSE);
505 GST_M3U8_LOCK (self);
507 /* check if the data changed since last update */
508 if (self->last_data && g_str_equal (self->last_data, data)) {
509 GST_DEBUG ("Playlist is the same as previous one");
511 GST_M3U8_UNLOCK (self);
515 if (!g_str_has_prefix (data, "#EXTM3U")) {
516 GST_WARNING ("Data doesn't start with #EXTM3U");
518 GST_M3U8_UNLOCK (self);
522 if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) {
523 GST_WARNING ("Not a media playlist, but a master playlist!");
524 GST_M3U8_UNLOCK (self);
528 GST_TRACE ("data:\n%s", data);
530 g_free (self->last_data);
531 self->last_data = data;
533 self->current_file = NULL;
534 previous_files = self->files;
536 self->duration = GST_CLOCK_TIME_NONE;
539 /* By default, allow caching */
540 self->allowcache = TRUE;
548 end = g_utf8_strchr (data, -1, '\n');
552 r = g_utf8_strchr (data, -1, '\r');
556 if (data[0] != '#' && data[0] != '\0') {
558 GST_LOG ("%s: got line without EXTINF, dropping", data);
562 data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
564 GstM3U8MediaFile *file;
565 file = gst_m3u8_media_file_new (data, title, duration, mediasequence++);
567 /* set encryption params */
568 file->key = current_key ? g_strdup (current_key) : NULL;
571 memcpy (file->iv, iv, sizeof (iv));
573 guint8 *iv = file->iv + 12;
574 GST_WRITE_UINT32_BE (iv, file->sequence);
581 file->offset = offset;
583 GstM3U8MediaFile *prev = self->files ? self->files->data : NULL;
588 offset = prev->offset + prev->size;
590 file->offset = offset;
597 file->discont = discontinuity;
598 #ifdef TIZEN_FEATURE_AD
599 timestamp += duration;
603 discontinuity = FALSE;
605 self->files = g_list_prepend (self->files, file);
608 } else if (g_str_has_prefix (data, "#EXTINF:")) {
610 if (!double_from_string (data + 8, &data, &fval)) {
611 GST_WARNING ("Can't read EXTINF duration");
614 duration = fval * (gdouble) GST_SECOND;
615 if (self->targetduration > 0 && duration > self->targetduration) {
616 GST_WARNING ("EXTINF duration (%" GST_TIME_FORMAT
617 ") > TARGETDURATION (%" GST_TIME_FORMAT ")",
618 GST_TIME_ARGS (duration), GST_TIME_ARGS (self->targetduration));
620 if (!data || *data != ',')
622 data = g_utf8_next_char (data);
625 title = g_strdup (data);
627 } else if (g_str_has_prefix (data, "#EXT-X-")) {
628 gchar *data_ext_x = data + 7;
630 /* All these entries start with #EXT-X- */
631 if (g_str_has_prefix (data_ext_x, "ENDLIST")) {
632 self->endlist = TRUE;
633 } else if (g_str_has_prefix (data_ext_x, "VERSION:")) {
634 if (int_from_string (data + 15, &data, &val))
636 } else if (g_str_has_prefix (data_ext_x, "TARGETDURATION:")) {
637 if (int_from_string (data + 22, &data, &val))
638 self->targetduration = val * GST_SECOND;
639 } else if (g_str_has_prefix (data_ext_x, "MEDIA-SEQUENCE:")) {
640 if (int_from_string (data + 22, &data, &val)) {
642 have_mediasequence = TRUE;
644 } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY-SEQUENCE:")) {
645 if (int_from_string (data + 30, &data, &val)
646 && val != self->discont_sequence) {
647 self->discont_sequence = val;
648 discontinuity = TRUE;
650 } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY")) {
651 self->discont_sequence++;
652 discontinuity = TRUE;
653 } else if (g_str_has_prefix (data_ext_x, "PROGRAM-DATE-TIME:")) {
654 /* <YYYY-MM-DDThh:mm:ssZ> */
655 GST_DEBUG ("FIXME parse date");
656 } else if (g_str_has_prefix (data_ext_x, "ALLOW-CACHE:")) {
657 self->allowcache = g_ascii_strcasecmp (data + 19, "YES") == 0;
658 } else if (g_str_has_prefix (data_ext_x, "KEY:")) {
663 /* IV and KEY are only valid until the next #EXT-X-KEY */
665 g_free (current_key);
667 while (data && parse_attributes (&data, &a, &v)) {
668 if (g_str_equal (a, "URI")) {
670 uri_join (self->base_uri ? self->base_uri : self->uri, v);
671 } else if (g_str_equal (a, "IV")) {
675 if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
676 && !g_str_has_prefix (ivp, "0X"))) {
677 GST_WARNING ("Can't read IV");
682 for (i = 0; i < 16; i++) {
685 h = g_ascii_xdigit_value (*ivp);
687 l = g_ascii_xdigit_value (*ivp);
689 if (h == -1 || l == -1) {
693 iv[i] = (h << 4) | l;
697 GST_WARNING ("Can't read IV");
701 } else if (g_str_equal (a, "METHOD")) {
702 if (!g_str_equal (v, "AES-128")) {
703 GST_WARNING ("Encryption method %s not supported", v);
708 } else if (g_str_has_prefix (data_ext_x, "BYTERANGE:")) {
709 gchar *v = data + 17;
711 if (int64_from_string (v, &v, &size)) {
712 if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
718 #ifdef TIZEN_FEATURE_AD
719 else if (g_str_has_prefix (data_ext_x, "CUE-OUT:")) {
723 GST_LOG ("cue out: %" GST_TIME_FORMAT ", %s", GST_TIME_ARGS (timestamp), data);
725 data = data + strlen ("#EXT-X-CUE-OUT:");
726 if (g_str_has_prefix (data, "DURATION="))
727 data = data + strlen ("DURATION=");
729 if (!double_from_string (data, &data, &fval)) {
730 GST_WARNING ("Can't read CUE-OUT duration");
734 duration = fval * (gdouble) GST_SECOND;
736 cue = gst_m3u8_cue_info_new (timestamp, duration);
737 GST_LOG ("cue out start %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT
738 , GST_TIME_ARGS (cue->start_time), GST_TIME_ARGS (cue->duration));
739 self->ad_info->cue = g_list_append (self->ad_info->cue, cue);
741 } else if (g_str_has_prefix (data_ext_x, "CUE-IN")) {
743 GstM3U8Cue *cue_data;
745 GST_LOG ("cue in: %" GST_TIME_FORMAT ", %s", GST_TIME_ARGS (timestamp), data);
747 cue = g_list_last (self->ad_info->cue);
748 if (!cue || !(cue->data)) {
749 GST_WARNING ("there is no valid data");
753 cue_data = cue->data;
754 GST_LOG ("start %" GST_TIME_FORMAT ", dur %" GST_TIME_FORMAT,
755 GST_TIME_ARGS (cue_data->start_time), GST_TIME_ARGS (cue_data->duration));
757 if (cue_data->end_time != 0) {
758 GST_WARNING ("cue syntax err, skip this tag.");
762 cue_data->end_time = timestamp;
764 GST_LOG ("cue start %" GST_TIME_FORMAT ", end %" GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
765 GST_TIME_ARGS (cue_data->start_time), GST_TIME_ARGS (cue_data->end_time),
766 GST_TIME_ARGS (cue_data->duration));
767 } else if (g_str_has_prefix (data_ext_x, "CUE-OUT-CONT:")) {
768 GstM3U8CueOutCont *cont = g_new0 (GstM3U8CueOutCont, 1);
770 GST_LOG ("cue cont: %" GST_TIME_FORMAT ", %s", GST_TIME_ARGS (timestamp), data);
772 data = data + strlen ("#EXT-X-CUE-OUT-CONT:");
773 cont->timestamp = timestamp;
774 cont->cont_data = g_strdup (data);
775 self->ad_info->cue_cont = g_list_append (self->ad_info->cue_cont, cont);
779 GST_LOG ("Ignored line: %s", data);
782 GST_LOG ("Ignored line: %s", data);
788 data = g_utf8_next_char (end); /* skip \n */
791 g_free (current_key);
794 self->files = g_list_reverse (self->files);
796 if (previous_files) {
797 gboolean consistent = TRUE;
799 if (have_mediasequence) {
800 consistent = check_media_seqnums (self, previous_files);
802 generate_media_seqnums (self, previous_files);
805 g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL);
806 g_list_free (previous_files);
807 previous_files = NULL;
809 /* error was reported above already */
811 GST_M3U8_UNLOCK (self);
816 if (self->files == NULL) {
817 GST_ERROR ("Invalid media playlist, it does not contain any media files");
818 GST_M3U8_UNLOCK (self);
822 /* calculate the start and end times of this media playlist. */
825 GstM3U8MediaFile *file;
826 GstClockTime duration = 0;
830 for (walk = self->files; walk; walk = walk->next) {
833 if (mediasequence == -1) {
834 mediasequence = file->sequence;
835 } else if (mediasequence >= file->sequence) {
836 GST_ERROR ("Non-increasing media sequence");
837 GST_M3U8_UNLOCK (self);
840 mediasequence = file->sequence;
843 duration += file->duration;
844 if (file->sequence > self->highest_sequence_number) {
845 if (self->highest_sequence_number >= 0) {
846 /* if an update of the media playlist has been missed, there
847 will be a gap between self->highest_sequence_number and the
848 first sequence number in this media playlist. In this situation
849 assume that the missing fragments had a duration of
850 targetduration each */
851 self->last_file_end +=
852 (file->sequence - self->highest_sequence_number -
853 1) * self->targetduration;
855 self->last_file_end += file->duration;
856 self->highest_sequence_number = file->sequence;
859 if (GST_M3U8_IS_LIVE (self)) {
860 self->first_file_start = self->last_file_end - duration;
861 GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %"
862 GST_TIME_FORMAT, GST_TIME_ARGS (self->first_file_start),
863 GST_TIME_ARGS (self->last_file_end));
865 self->duration = duration;
868 /* first-time setup */
869 if (self->files && self->sequence == -1) {
872 if (GST_M3U8_IS_LIVE (self)) {
874 GstClockTime sequence_pos = 0;
876 file = g_list_last (self->files);
878 if (self->last_file_end >= GST_M3U8_MEDIA_FILE (file->data)->duration) {
880 self->last_file_end - GST_M3U8_MEDIA_FILE (file->data)->duration;
883 /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
884 * the end of the playlist. See section 6.3.3 of HLS draft */
885 for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE && file->prev &&
886 GST_M3U8_MEDIA_FILE (file->prev->data)->duration <= sequence_pos;
889 sequence_pos -= GST_M3U8_MEDIA_FILE (file->data)->duration;
891 self->sequence_position = sequence_pos;
893 file = g_list_first (self->files);
894 self->sequence_position = 0;
896 self->current_file = file;
897 self->sequence = GST_M3U8_MEDIA_FILE (file->data)->sequence;
898 GST_DEBUG ("first sequence: %u", (guint) self->sequence);
901 GST_LOG ("processed media playlist %s, %u fragments", self->name,
902 g_list_length (self->files));
904 GST_M3U8_UNLOCK (self);
909 /* call with M3U8_LOCK held */
911 m3u8_find_next_fragment (GstM3U8 * m3u8, gboolean forward)
913 GstM3U8MediaFile *file;
914 GList *l = m3u8->files;
920 if (file->sequence >= m3u8->sequence)
931 if (file->sequence <= m3u8->sequence)
942 gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward,
943 GstClockTime * sequence_position, gboolean * discont)
945 GstM3U8MediaFile *file = NULL;
947 g_return_val_if_fail (m3u8 != NULL, NULL);
949 GST_M3U8_LOCK (m3u8);
951 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
953 if (m3u8->sequence < 0) /* can't happen really */
956 if (m3u8->current_file == NULL)
957 m3u8->current_file = m3u8_find_next_fragment (m3u8, forward);
959 if (m3u8->current_file == NULL)
962 file = gst_m3u8_media_file_ref (m3u8->current_file->data);
964 GST_DEBUG ("Got fragment with sequence %u (current sequence %u)",
965 (guint) file->sequence, (guint) m3u8->sequence);
967 if (sequence_position)
968 *sequence_position = m3u8->sequence_position;
970 *discont = file->discont || (m3u8->sequence != file->sequence);
972 m3u8->current_file_duration = file->duration;
973 m3u8->sequence = file->sequence;
977 GST_M3U8_UNLOCK (m3u8);
983 gst_m3u8_has_next_fragment (GstM3U8 * m3u8, gboolean forward)
988 g_return_val_if_fail (m3u8 != NULL, FALSE);
990 GST_M3U8_LOCK (m3u8);
992 GST_DEBUG ("Checking next fragment %" G_GINT64_FORMAT,
993 m3u8->sequence + (forward ? 1 : -1));
995 if (m3u8->current_file) {
996 cur = m3u8->current_file;
998 cur = m3u8_find_next_fragment (m3u8, forward);
1001 have_next = cur && ((forward && cur->next) || (!forward && cur->prev));
1003 GST_M3U8_UNLOCK (m3u8);
1008 /* call with M3U8_LOCK held */
1010 m3u8_alternate_advance (GstM3U8 * m3u8, gboolean forward)
1012 gint targetnum = m3u8->sequence;
1014 GstM3U8MediaFile *mf;
1016 /* figure out the target seqnum */
1022 for (tmp = m3u8->files; tmp; tmp = tmp->next) {
1023 mf = (GstM3U8MediaFile *) tmp->data;
1024 if (mf->sequence == targetnum)
1028 GST_WARNING ("Can't find next fragment");
1031 m3u8->current_file = tmp;
1032 m3u8->sequence = targetnum;
1033 m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (tmp->data)->duration;
1037 gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
1039 GstM3U8MediaFile *file;
1041 g_return_if_fail (m3u8 != NULL);
1043 GST_M3U8_LOCK (m3u8);
1045 GST_DEBUG ("Sequence position was %" GST_TIME_FORMAT,
1046 GST_TIME_ARGS (m3u8->sequence_position));
1047 if (GST_CLOCK_TIME_IS_VALID (m3u8->current_file_duration)) {
1048 /* Advance our position based on the previous fragment we played */
1050 m3u8->sequence_position += m3u8->current_file_duration;
1051 else if (m3u8->current_file_duration < m3u8->sequence_position)
1052 m3u8->sequence_position -= m3u8->current_file_duration;
1054 m3u8->sequence_position = 0;
1055 m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
1056 GST_DEBUG ("Sequence position now %" GST_TIME_FORMAT,
1057 GST_TIME_ARGS (m3u8->sequence_position));
1059 if (!m3u8->current_file) {
1062 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
1063 for (l = m3u8->files; l != NULL; l = l->next) {
1064 if (GST_M3U8_MEDIA_FILE (l->data)->sequence == m3u8->sequence) {
1065 m3u8->current_file = l;
1069 if (m3u8->current_file == NULL) {
1071 ("Could not find current fragment, trying next fragment directly");
1072 m3u8_alternate_advance (m3u8, forward);
1074 /* Resync sequence number if the above has failed for live streams */
1075 if (m3u8->current_file == NULL && GST_M3U8_IS_LIVE (m3u8)) {
1076 /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
1077 the end of the playlist. See section 6.3.3 of HLS draft */
1079 g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
1080 m3u8->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
1081 m3u8->current_file_duration =
1082 GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
1084 GST_WARNING ("Resyncing live playlist");
1090 file = GST_M3U8_MEDIA_FILE (m3u8->current_file->data);
1091 GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
1093 m3u8->current_file = m3u8->current_file->next;
1094 if (m3u8->current_file) {
1095 m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
1097 m3u8->sequence = file->sequence + 1;
1100 m3u8->current_file = m3u8->current_file->prev;
1101 if (m3u8->current_file) {
1102 m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
1104 m3u8->sequence = file->sequence - 1;
1107 if (m3u8->current_file) {
1108 /* Store duration of the fragment we're using to update the position
1109 * the next time we advance */
1110 m3u8->current_file_duration =
1111 GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
1116 GST_M3U8_UNLOCK (m3u8);
1120 gst_m3u8_get_duration (GstM3U8 * m3u8)
1122 GstClockTime duration = GST_CLOCK_TIME_NONE;
1124 g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1126 GST_M3U8_LOCK (m3u8);
1128 /* We can only get the duration for on-demand streams */
1132 if (!GST_CLOCK_TIME_IS_VALID (m3u8->duration) && m3u8->files != NULL) {
1136 for (f = m3u8->files; f != NULL; f = f->next)
1137 m3u8->duration += GST_M3U8_MEDIA_FILE (f)->duration;
1139 duration = m3u8->duration;
1143 GST_M3U8_UNLOCK (m3u8);
1149 gst_m3u8_get_target_duration (GstM3U8 * m3u8)
1151 GstClockTime target_duration;
1153 g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1155 GST_M3U8_LOCK (m3u8);
1156 target_duration = m3u8->targetduration;
1157 GST_M3U8_UNLOCK (m3u8);
1159 return target_duration;
1163 gst_m3u8_get_uri (GstM3U8 * m3u8)
1167 GST_M3U8_LOCK (m3u8);
1168 uri = g_strdup (m3u8->uri);
1169 GST_M3U8_UNLOCK (m3u8);
1175 gst_m3u8_is_live (GstM3U8 * m3u8)
1179 g_return_val_if_fail (m3u8 != NULL, FALSE);
1181 GST_M3U8_LOCK (m3u8);
1182 is_live = GST_M3U8_IS_LIVE (m3u8);
1183 GST_M3U8_UNLOCK (m3u8);
1189 uri_join (const gchar * uri1, const gchar * uri2)
1191 gchar *uri_copy, *tmp, *ret = NULL;
1193 if (gst_uri_is_valid (uri2))
1194 return g_strdup (uri2);
1196 uri_copy = g_strdup (uri1);
1197 if (uri2[0] != '/') {
1198 /* uri2 is a relative uri2 */
1199 /* look for query params */
1200 tmp = g_utf8_strchr (uri_copy, -1, '?');
1202 /* find last / char, ignoring query params */
1203 tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
1205 /* find last / char in URL */
1206 tmp = g_utf8_strrchr (uri_copy, -1, '/');
1209 GST_WARNING ("Can't build a valid uri_copy");
1214 ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
1216 /* uri2 is an absolute uri2 */
1217 char *scheme, *hostname;
1220 /* find the : in <scheme>:// */
1221 tmp = g_utf8_strchr (uri_copy, -1, ':');
1223 GST_WARNING ("Can't build a valid uri_copy");
1232 tmp = g_utf8_strchr (hostname, -1, '/');
1236 ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
1245 gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop)
1247 GstClockTime duration = 0;
1249 GstM3U8MediaFile *file;
1251 guint min_distance = 0;
1253 g_return_val_if_fail (m3u8 != NULL, FALSE);
1255 GST_M3U8_LOCK (m3u8);
1257 if (m3u8->files == NULL)
1260 if (GST_M3U8_IS_LIVE (m3u8)) {
1261 /* min_distance is used to make sure the seek range is never closer than
1262 GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of a live
1263 playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
1264 min_distance = GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
1266 count = g_list_length (m3u8->files);
1268 for (walk = m3u8->files; walk && count > min_distance; walk = walk->next) {
1271 duration += file->duration;
1277 *start = m3u8->first_file_start;
1278 *stop = *start + duration;
1282 GST_M3U8_UNLOCK (m3u8);
1283 return (duration > 0);
1287 gst_hls_media_ref (GstHLSMedia * media)
1289 g_assert (media != NULL && media->ref_count > 0);
1290 g_atomic_int_add (&media->ref_count, 1);
1295 gst_hls_media_unref (GstHLSMedia * media)
1297 g_assert (media != NULL && media->ref_count > 0);
1298 if (g_atomic_int_dec_and_test (&media->ref_count)) {
1299 if (media->playlist)
1300 gst_m3u8_unref (media->playlist);
1301 g_free (media->group_id);
1302 g_free (media->name);
1303 g_free (media->uri);
1304 g_free (media->lang);
1309 static GstHLSMediaType
1310 gst_m3u8_get_hls_media_type_from_string (const gchar * type_name)
1312 if (strcmp (type_name, "AUDIO") == 0)
1313 return GST_HLS_MEDIA_TYPE_AUDIO;
1314 if (strcmp (type_name, "VIDEO") == 0)
1315 return GST_HLS_MEDIA_TYPE_VIDEO;
1316 if (strcmp (type_name, "SUBTITLES") == 0)
1317 return GST_HLS_MEDIA_TYPE_SUBTITLES;
1318 if (strcmp (type_name, "CLOSED_CAPTIONS") == 0)
1319 return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS;
1321 return GST_HLS_MEDIA_TYPE_INVALID;
1324 #define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_m3u8_hls_media_type_get_nick(mtype)
1325 static inline const gchar *
1326 gst_m3u8_hls_media_type_get_nick (GstHLSMediaType mtype)
1328 static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video",
1329 "subtitle", "closed-captions"
1332 if (mtype < 0 || mtype >= GST_HLS_N_MEDIA_TYPES)
1335 return nicks[mtype];
1338 /* returns unquoted copy of string */
1340 gst_m3u8_unquote (const gchar * str)
1342 const gchar *start, *end;
1344 start = strchr (str, '"');
1346 return g_strdup (str);
1347 end = strchr (start + 1, '"');
1349 GST_WARNING ("Broken quoted string [%s] - can't find end quote", str);
1350 return g_strdup (start + 1);
1352 return g_strndup (start + 1, (gsize) (end - (start + 1)));
1355 static GstHLSMedia *
1356 gst_m3u8_parse_media (gchar * desc, const gchar * base_uri)
1361 media = g_new0 (GstHLSMedia, 1);
1362 media->ref_count = 1;
1363 media->playlist = gst_m3u8_new ();
1364 media->mtype = GST_HLS_MEDIA_TYPE_INVALID;
1366 GST_LOG ("parsing %s", desc);
1367 while (desc != NULL && parse_attributes (&desc, &a, &v)) {
1368 if (strcmp (a, "TYPE") == 0) {
1369 media->mtype = gst_m3u8_get_hls_media_type_from_string (v);
1370 } else if (strcmp (a, "GROUP-ID") == 0) {
1371 g_free (media->group_id);
1372 media->group_id = gst_m3u8_unquote (v);
1373 } else if (strcmp (a, "NAME") == 0) {
1374 g_free (media->name);
1375 media->name = gst_m3u8_unquote (v);
1376 } else if (strcmp (a, "URI") == 0) {
1379 g_free (media->uri);
1380 uri = gst_m3u8_unquote (v);
1381 media->uri = uri_join (base_uri, uri);
1383 } else if (strcmp (a, "LANGUAGE") == 0) {
1384 g_free (media->lang);
1385 media->lang = gst_m3u8_unquote (v);
1386 } else if (strcmp (a, "DEFAULT") == 0) {
1387 media->is_default = g_ascii_strcasecmp (v, "yes") == 0;
1388 } else if (strcmp (a, "FORCED") == 0) {
1389 media->forced = g_ascii_strcasecmp (v, "yes") == 0;
1390 } else if (strcmp (a, "AUTOSELECT") == 0) {
1391 media->autoselect = g_ascii_strcasecmp (v, "yes") == 0;
1393 /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */
1394 GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v);
1398 if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID)
1399 goto required_attributes_missing;
1401 #ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
1402 if (media->uri == NULL)
1403 goto existing_stream;
1406 if (media->group_id == NULL || media->name == NULL)
1407 goto required_attributes_missing;
1409 #ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
1410 if (media->mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS)
1414 GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s",
1415 GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name,
1416 media->uri, media->is_default ? "default" : "-",
1417 media->autoselect ? "autoselect" : "-",
1418 media->forced ? "forced" : "-", media->lang ? media->lang : "??");
1422 #ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
1425 GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified");
1429 required_attributes_missing:
1431 GST_WARNING ("EXT-X-MEDIA description is missing required attributes");
1435 #ifndef TIZEN_FEATURE_HLSDEMUX_LANG_TAG
1438 GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping");
1445 gst_hls_media_unref (media);
1450 static GstHLSVariantStream *
1451 gst_hls_variant_stream_new (void)
1453 GstHLSVariantStream *stream;
1455 stream = g_new0 (GstHLSVariantStream, 1);
1456 stream->m3u8 = gst_m3u8_new ();
1457 stream->refcount = 1;
1461 GstHLSVariantStream *
1462 gst_hls_variant_stream_ref (GstHLSVariantStream * stream)
1464 g_atomic_int_inc (&stream->refcount);
1469 gst_hls_variant_stream_unref (GstHLSVariantStream * stream)
1471 if (g_atomic_int_dec_and_test (&stream->refcount)) {
1474 g_free (stream->name);
1475 g_free (stream->uri);
1476 g_free (stream->codecs);
1477 gst_m3u8_unref (stream->m3u8);
1478 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1479 g_free (stream->media_groups[i]);
1480 g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref);
1486 static GstHLSVariantStream *
1487 find_variant_stream_by_name (GList * list, const gchar * name)
1489 for (; list != NULL; list = list->next) {
1490 GstHLSVariantStream *variant_stream = list->data;
1492 if (variant_stream->name != NULL && !strcmp (variant_stream->name, name))
1493 return variant_stream;
1498 static GstHLSVariantStream *
1499 find_variant_stream_by_uri (GList * list, const gchar * uri)
1501 for (; list != NULL; list = list->next) {
1502 GstHLSVariantStream *variant_stream = list->data;
1504 if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri))
1505 return variant_stream;
1510 static GstHLSMasterPlaylist *
1511 gst_hls_master_playlist_new (void)
1513 GstHLSMasterPlaylist *playlist;
1515 playlist = g_new0 (GstHLSMasterPlaylist, 1);
1516 playlist->refcount = 1;
1517 playlist->is_simple = FALSE;
1523 gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist)
1525 if (g_atomic_int_dec_and_test (&playlist->refcount)) {
1526 g_list_free_full (playlist->variants,
1527 (GDestroyNotify) gst_hls_variant_stream_unref);
1528 g_list_free_full (playlist->iframe_variants,
1529 (GDestroyNotify) gst_hls_variant_stream_unref);
1530 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
1531 g_list_free_full (playlist->variant_info, g_free);
1533 if (playlist->default_variant)
1534 gst_hls_variant_stream_unref (playlist->default_variant);
1535 g_free (playlist->last_data);
1541 hls_media_name_compare_func (gconstpointer media, gconstpointer name)
1543 return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name);
1546 /* Takes ownership of @data */
1547 GstHLSMasterPlaylist *
1548 gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
1550 GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, };
1551 GstHLSMasterPlaylist *playlist;
1552 GstHLSVariantStream *pending_stream;
1553 gchar *end, *free_data = data;
1557 if (!g_str_has_prefix (data, "#EXTM3U")) {
1558 GST_WARNING ("Data doesn't start with #EXTM3U");
1563 playlist = gst_hls_master_playlist_new ();
1565 /* store data before we modify it for parsing */
1566 playlist->last_data = g_strdup (data);
1568 GST_TRACE ("data:\n%s", data);
1570 if (strstr (data, "\n#EXTINF:") != NULL) {
1571 GST_INFO ("This is a simple media playlist, not a master playlist");
1573 pending_stream = gst_hls_variant_stream_new ();
1574 pending_stream->name = g_strdup (base_uri);
1575 pending_stream->uri = g_strdup (base_uri);
1576 gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri);
1577 playlist->variants = g_list_append (playlist->variants, pending_stream);
1578 playlist->default_variant = gst_hls_variant_stream_ref (pending_stream);
1579 playlist->is_simple = TRUE;
1581 if (!gst_m3u8_update (pending_stream->m3u8, data)) {
1582 GST_WARNING ("Failed to parse media playlist");
1583 gst_hls_master_playlist_unref (playlist);
1589 pending_stream = NULL;
1594 end = g_utf8_strchr (data, -1, '\n');
1598 r = g_utf8_strchr (data, -1, '\r');
1602 if (data[0] != '#' && data[0] != '\0') {
1605 if (pending_stream == NULL) {
1606 GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data);
1611 uri = uri_join (base_uri, name);
1615 pending_stream->name = g_strdup (name);
1616 pending_stream->uri = uri;
1618 if (find_variant_stream_by_name (playlist->variants, name)
1619 || find_variant_stream_by_uri (playlist->variants, uri)) {
1620 GST_DEBUG ("Already have a list with this name or URI: %s", name);
1621 gst_hls_variant_stream_unref (pending_stream);
1623 GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri);
1624 gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name);
1625 playlist->variants = g_list_append (playlist->variants, pending_stream);
1626 /* use first stream in the playlist as default */
1627 if (playlist->default_variant == NULL) {
1628 playlist->default_variant =
1629 gst_hls_variant_stream_ref (pending_stream);
1632 pending_stream = NULL;
1633 } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
1634 if (int_from_string (data + 15, &data, &val))
1635 playlist->version = val;
1636 } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
1637 g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
1638 GstHLSVariantStream *stream;
1641 stream = gst_hls_variant_stream_new ();
1642 stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
1643 data += stream->iframe ? 26 : 18;
1644 while (data && parse_attributes (&data, &a, &v)) {
1645 if (g_str_equal (a, "BANDWIDTH")) {
1646 if (!stream->bandwidth) {
1647 if (!int_from_string (v, NULL, &stream->bandwidth))
1648 GST_WARNING ("Error while reading BANDWIDTH");
1650 } else if (g_str_equal (a, "AVERAGE-BANDWIDTH")) {
1652 ("AVERAGE-BANDWIDTH attribute available. Using it as stream bandwidth");
1653 if (!int_from_string (v, NULL, &stream->bandwidth))
1654 GST_WARNING ("Error while reading AVERAGE-BANDWIDTH");
1655 } else if (g_str_equal (a, "PROGRAM-ID")) {
1656 if (!int_from_string (v, NULL, &stream->program_id))
1657 GST_WARNING ("Error while reading PROGRAM-ID");
1658 } else if (g_str_equal (a, "CODECS")) {
1659 g_free (stream->codecs);
1660 stream->codecs = g_strdup (v);
1661 } else if (g_str_equal (a, "RESOLUTION")) {
1662 if (!int_from_string (v, &v, &stream->width))
1663 GST_WARNING ("Error while reading RESOLUTION width");
1664 if (!v || *v != 'x') {
1665 GST_WARNING ("Missing height");
1667 v = g_utf8_next_char (v);
1668 if (!int_from_string (v, NULL, &stream->height))
1669 GST_WARNING ("Error while reading RESOLUTION height");
1671 } else if (stream->iframe && g_str_equal (a, "URI")) {
1672 stream->uri = uri_join (base_uri, v);
1673 if (stream->uri != NULL) {
1674 stream->name = g_strdup (stream->uri);
1675 gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name);
1677 gst_hls_variant_stream_unref (stream);
1679 } else if (g_str_equal (a, "AUDIO")) {
1680 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]);
1681 stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v);
1682 } else if (g_str_equal (a, "SUBTITLES")) {
1683 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]);
1684 stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] =
1685 gst_m3u8_unquote (v);
1686 } else if (g_str_equal (a, "VIDEO")) {
1687 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]);
1688 stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v);
1689 } else if (g_str_equal (a, "CLOSED-CAPTIONS")) {
1690 /* closed captions will be embedded inside the video stream, ignore */
1694 if (stream->iframe) {
1695 if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) {
1696 GST_DEBUG ("Already have a list with this URI");
1697 gst_hls_variant_stream_unref (stream);
1699 playlist->iframe_variants =
1700 g_list_append (playlist->iframe_variants, stream);
1703 if (pending_stream != NULL) {
1704 GST_WARNING ("variant stream without uri, dropping");
1705 gst_hls_variant_stream_unref (pending_stream);
1707 pending_stream = stream;
1709 } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) {
1713 media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri);
1718 if (media_groups[media->mtype] == NULL) {
1719 media_groups[media->mtype] =
1720 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1723 list = g_hash_table_lookup (media_groups[media->mtype], media->group_id);
1725 /* make sure there isn't already a media with the same name */
1726 if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) {
1727 g_hash_table_replace (media_groups[media->mtype],
1728 g_strdup (media->group_id), g_list_append (list, media));
1729 GST_INFO ("Added media %s to group %s", media->name, media->group_id);
1731 GST_WARNING (" media with name '%s' already exists in group '%s'!",
1732 media->name, media->group_id);
1733 gst_hls_media_unref (media);
1735 } else if (*data != '\0') {
1736 GST_LOG ("Ignored line: %s", data);
1742 data = g_utf8_next_char (end); /* skip \n */
1745 if (pending_stream != NULL) {
1746 GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping");
1747 gst_hls_variant_stream_unref (pending_stream);
1752 /* Add alternative renditions media to variant streams */
1753 for (l = playlist->variants; l != NULL; l = l->next) {
1754 GstHLSVariantStream *stream = l->data;
1757 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1758 if (stream->media_groups[i] != NULL && media_groups[i] != NULL) {
1759 GST_INFO ("Adding %s group '%s' to stream '%s'",
1760 GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name);
1762 mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]);
1765 GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]);
1767 while (mlist != NULL) {
1768 GstHLSMedia *media = mlist->data;
1770 GST_DEBUG (" %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i),
1771 media->name, media->uri);
1774 g_list_append (stream->media[i], gst_hls_media_ref (media));
1775 mlist = mlist->next;
1781 /* clean up our temporary alternative rendition groups hash tables */
1782 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1783 if (media_groups[i] != NULL) {
1784 GList *groups, *mlist;
1786 groups = g_hash_table_get_keys (media_groups[i]);
1787 for (l = groups; l != NULL; l = l->next) {
1788 mlist = g_hash_table_lookup (media_groups[i], l->data);
1789 g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref);
1791 g_list_free (groups);
1792 g_hash_table_unref (media_groups[i]);
1796 if (playlist->variants == NULL) {
1797 GST_WARNING ("Master playlist without any media playlists!");
1798 gst_hls_master_playlist_unref (playlist);
1802 /* reorder variants by bitrate */
1803 playlist->variants =
1804 g_list_sort (playlist->variants,
1805 (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1807 playlist->iframe_variants =
1808 g_list_sort (playlist->iframe_variants,
1809 (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1811 /* FIXME: restore old current_variant after master playlist update
1812 * (move into code that does that update) */
1815 gchar *top_variant_uri = NULL;
1816 gboolean iframe = FALSE;
1818 if (!self->current_variant) {
1819 top_variant_uri = GST_M3U8 (self->lists->data)->uri;
1821 top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
1822 iframe = GST_M3U8 (self->current_variant->data)->iframe;
1825 /* here we sorted the lists */
1828 playlist->current_variant =
1829 find_variant_stream_by_uri (playlist->iframe_variants,
1832 playlist->current_variant =
1833 find_variant_stream_by_uri (playlist->variants, top_variant_uri);
1837 GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams",
1838 g_list_length (playlist->variants),
1839 g_list_length (playlist->iframe_variants));
1841 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
1842 GList *v = (playlist->iframe_variants)?(playlist->iframe_variants):(playlist->variants);
1844 /* update variant stream info */
1845 for (; v != NULL; v = v->next) {
1846 GstHLSVariantStream *data = v->data;
1847 GstM3U8VideoVariantInfo *var_info = g_new0 (GstM3U8VideoVariantInfo, 1);
1849 GST_LOG ("variant info %d, %d x %d", data->bandwidth, data->width,
1851 var_info->bandwidth = data->bandwidth;
1852 var_info->width = data->width;
1853 var_info->height = data->height;
1855 playlist->variant_info = g_list_append (playlist->variant_info, var_info);
1863 gst_hls_variant_stream_is_live (GstHLSVariantStream * variant)
1867 g_return_val_if_fail (variant != NULL, FALSE);
1869 is_live = gst_m3u8_is_live (variant->m3u8);
1875 compare_media (const GstHLSMedia * a, const GstHLSMedia * b)
1877 return strcmp (a->name, b->name);
1881 gst_hls_variant_find_matching_media (GstHLSVariantStream * stream,
1882 GstHLSMedia * media)
1884 GList *mlist = stream->media[media->mtype];
1890 match = g_list_find_custom (mlist, media, (GCompareFunc) compare_media);
1897 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
1899 get_num_of_codec(GstHLSVariantStream * variant)
1901 #define MAX_NUM_OF_CODEC 10
1904 gchar** codec_list = NULL;
1906 if (!variant || !variant->codecs)
1909 codec_list = g_strsplit (variant->codecs, ",", MAX_NUM_OF_CODEC);
1911 cnt = g_strv_length (codec_list);
1912 g_strfreev (codec_list);
1919 check_num_of_codec(GstHLSVariantStream * variant, guint req_num)
1921 guint num_of_codec = 0;
1926 num_of_codec = get_num_of_codec (variant);
1928 if (num_of_codec > 0 && req_num > 0 && num_of_codec != req_num) {
1929 GST_WARNING ("can not support to change codec");
1937 update_max_limit(gint min_limit, gint max_limit, gint start_limit, gint bitrate)
1939 if (start_limit > DEFAULT_BANDWIDTH_LIMIT && start_limit >= min_limit &&
1940 (start_limit <= max_limit || max_limit == DEFAULT_BANDWIDTH_LIMIT)) {
1941 GST_DEBUG ("set max to start : %d ", start_limit);
1945 if (bitrate > 0 && bitrate >= min_limit &&
1946 (bitrate <= max_limit || max_limit == DEFAULT_BANDWIDTH_LIMIT)) {
1947 GST_DEBUG ("set max to bitrate : %d ", bitrate);
1951 if (bitrate == 0 || bitrate < min_limit) {
1952 GST_DEBUG ("set max to min : %d", min_limit);
1959 GstHLSVariantStream *
1960 get_average_variant(GList *variants, guint num_of_codec, gint min_limit, gint max_limit)
1963 GList *valid_list = NULL;
1965 guint num_of_valid_variant = 0;
1966 GstHLSVariantStream *tmp = NULL;
1968 for (l = g_list_first (variants); l; l = g_list_next (l)) {
1969 if (!check_num_of_codec ((GstHLSVariantStream *)l->data, num_of_codec))
1974 if (max_limit != DEFAULT_BANDWIDTH_LIMIT && tmp->bandwidth > max_limit) {
1975 GST_DEBUG ("over max limit");
1979 if (min_limit != DEFAULT_BANDWIDTH_LIMIT && tmp->bandwidth < min_limit) {
1980 GST_DEBUG ("skip to next");
1984 num_of_valid_variant++;
1988 GST_DEBUG ("num of valid variant %d / %d", num_of_valid_variant, g_list_length (variants));
1990 for (; valid_list; valid_list = g_list_previous (valid_list)) {
1991 if (!check_num_of_codec ((GstHLSVariantStream *)valid_list->data, num_of_codec))
1994 tmp = valid_list->data;
1995 if (num_of_valid_variant/2 == cnt) {
1996 GST_DEBUG ("get this stream %d", tmp->bandwidth);
2004 GstHLSVariantStream *
2005 gst_hls_master_playlist_get_variant_for_bandwitdh_limit (GstHLSMasterPlaylist * playlist,
2006 GstHLSVariantStream * current_variant, guint bitrate, gchar * start_bandwidth,
2007 gint min_bandwidth, gint max_bandwidth, gint width, gint height)
2009 GstHLSVariantStream *tmp = current_variant;
2010 GstHLSVariantStream *variant = NULL;
2011 GstHLSVariantStream *min_variant = NULL; // lowest
2012 GstHLSVariantStream *max_variant = NULL; // highest
2013 GList *variants = NULL;
2015 gint max_limit = DEFAULT_BANDWIDTH_LIMIT;
2016 gint min_limit = DEFAULT_BANDWIDTH_LIMIT;
2017 gint start_limit = DEFAULT_BANDWIDTH_LIMIT;
2018 gint adj_max_limit = DEFAULT_BANDWIDTH_LIMIT;
2019 guint num_of_valid_variant = 0;
2020 guint num_of_codec = 0;
2022 num_of_codec = get_num_of_codec (current_variant);
2024 GST_DEBUG ("bitrate: %u, bandwidth: %s, %d ~ %d, resolution: %d X %d",
2025 bitrate, start_bandwidth, min_bandwidth, max_bandwidth, width, height);
2027 /* get variant list */
2028 if (current_variant == NULL || !current_variant->iframe)
2029 variants = playlist->variants;
2031 variants = playlist->iframe_variants;
2034 GST_ERROR ("invalid playlist");
2035 return current_variant;
2038 /* get valid min/max variant */
2039 for (l = g_list_first (variants); l; l = g_list_next (l)) {
2040 if (!check_num_of_codec ((GstHLSVariantStream *)l->data, num_of_codec))
2043 num_of_valid_variant++;
2051 GST_DEBUG("num of valid variant %d / %d", num_of_valid_variant, g_list_length (variants));
2052 if (num_of_valid_variant <= 1)
2055 /* get valid range limit */
2056 if (max_bandwidth == DEFAULT_BANDWIDTH_LIMIT || min_bandwidth <= max_bandwidth) {
2057 if (min_variant->bandwidth <= max_bandwidth)
2058 max_limit = adj_max_limit = max_bandwidth;
2060 if (max_variant->bandwidth >= min_bandwidth)
2061 min_limit = min_bandwidth;
2064 GST_DEBUG ("range limit: %d ~ %d", min_limit, max_limit);
2066 if (start_bandwidth) {
2067 if (!g_strcmp0 (start_bandwidth, "LOWEST")) {
2068 if (min_limit == DEFAULT_BANDWIDTH_LIMIT)
2070 adj_max_limit = min_limit;
2071 } else if (!g_strcmp0 (start_bandwidth, "HIGHEST")) {
2072 if (max_limit == DEFAULT_BANDWIDTH_LIMIT)
2074 } else if (!g_strcmp0 (start_bandwidth, "AVERAGE")) {
2075 variant = get_average_variant (variants, num_of_codec, min_limit, max_limit);
2079 start_limit = atoi (start_bandwidth);
2080 /* update max limit based on the start_bandwidth or network bitrate */
2081 adj_max_limit = update_max_limit (min_limit, max_limit, start_limit, bitrate);
2084 /* update max limit based on the network bitrate */
2085 adj_max_limit = update_max_limit (min_limit, max_limit, DEFAULT_BANDWIDTH_LIMIT, bitrate);
2088 if (min_limit < 0 && adj_max_limit < 0 && width < 0 && height < 0) {
2089 GST_WARNING ("invalid condition, get default variant");
2093 GST_DEBUG ("adj range limit: %d ~ %d (origin: %d)", min_limit, adj_max_limit, max_limit);
2095 /* variant lists are sorted low to high, so iterate from highest to lowest */
2097 for (l = g_list_last (variants); l; l = g_list_previous (l)) {
2098 if (!check_num_of_codec ((GstHLSVariantStream *)l->data, num_of_codec))
2102 GST_DEBUG ("stream info: %d, %d x %d", tmp->bandwidth, tmp->width, tmp->height);
2104 if (tmp->bandwidth < min_limit) {
2105 GList *j = g_list_next(l);
2110 ((max_limit == DEFAULT_BANDWIDTH_LIMIT) ||
2111 ((GstHLSVariantStream*)j->data)->bandwidth <= max_limit))
2112 variant = j->data; /* get the lowest one in the valid range */
2118 if (adj_max_limit > DEFAULT_BANDWIDTH_LIMIT && adj_max_limit < tmp->bandwidth)
2121 if (((width > DEFAULT_RESOLUTION_LIMIT) && (tmp->width > width)) ||
2122 ((height > DEFAULT_RESOLUTION_LIMIT) && (tmp->height > height))) {
2123 if (adj_max_limit > DEFAULT_BANDWIDTH_LIMIT && !variant) { /* will be kept with the first one with the same bitrate */
2128 GST_DEBUG ("get this stream %d", variant->bandwidth);
2133 return (variant)?(variant):(tmp);
2137 GstHLSVariantStream *
2138 gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
2139 playlist, GstHLSVariantStream * current_variant, guint bitrate)
2141 GstHLSVariantStream *variant = current_variant;
2144 /* variant lists are sorted low to high, so iterate from highest to lowest */
2145 if (current_variant == NULL || !current_variant->iframe)
2146 l = g_list_last (playlist->variants);
2148 l = g_list_last (playlist->iframe_variants);
2152 if (variant->bandwidth <= bitrate)
2161 GstHLSVariantStream *
2162 gst_hls_master_playlist_get_matching_variant (GstHLSMasterPlaylist * playlist,
2163 GstHLSVariantStream * current_variant)
2165 if (current_variant->iframe) {
2166 return find_variant_stream_by_uri (playlist->iframe_variants,
2167 current_variant->uri);
2170 return find_variant_stream_by_uri (playlist->variants, current_variant->uri);