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_ADAPTIVE_VARIANT -1
37 static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
38 gchar * title, GstClockTime duration, guint sequence);
39 static gchar *uri_join (const gchar * uri, const gchar * path);
46 m3u8 = g_new0 (GstM3U8, 1);
48 m3u8->current_file = NULL;
49 m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
51 m3u8->sequence_position = 0;
52 m3u8->highest_sequence_number = -1;
53 m3u8->duration = GST_CLOCK_TIME_NONE;
55 g_mutex_init (&m3u8->lock);
61 /* call with M3U8_LOCK held */
63 gst_m3u8_take_uri (GstM3U8 * self, gchar * uri, gchar * base_uri, gchar * name)
65 g_return_if_fail (self != NULL);
67 if (self->uri != uri) {
71 if (self->base_uri != base_uri) {
72 g_free (self->base_uri);
73 self->base_uri = base_uri;
75 if (self->name != name) {
82 gst_m3u8_set_uri (GstM3U8 * m3u8, const gchar * uri, const gchar * base_uri,
86 gst_m3u8_take_uri (m3u8, g_strdup (uri), g_strdup (base_uri),
88 GST_M3U8_UNLOCK (m3u8);
92 gst_m3u8_ref (GstM3U8 * m3u8)
94 g_assert (m3u8 != NULL && m3u8->ref_count > 0);
96 g_atomic_int_add (&m3u8->ref_count, 1);
101 gst_m3u8_unref (GstM3U8 * self)
103 g_return_if_fail (self != NULL && self->ref_count > 0);
105 if (g_atomic_int_dec_and_test (&self->ref_count)) {
107 g_free (self->base_uri);
110 g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_unref, NULL);
111 g_list_free (self->files);
113 g_free (self->last_data);
118 static GstM3U8MediaFile *
119 gst_m3u8_media_file_new (gchar * uri, gchar * title, GstClockTime duration,
122 GstM3U8MediaFile *file;
124 file = g_new0 (GstM3U8MediaFile, 1);
127 file->duration = duration;
128 file->sequence = sequence;
135 gst_m3u8_media_file_ref (GstM3U8MediaFile * mfile)
137 g_assert (mfile != NULL && mfile->ref_count > 0);
139 g_atomic_int_add (&mfile->ref_count, 1);
144 gst_m3u8_media_file_unref (GstM3U8MediaFile * self)
146 g_return_if_fail (self != NULL && self->ref_count > 0);
148 if (g_atomic_int_dec_and_test (&self->ref_count)) {
149 g_free (self->title);
157 int_from_string (gchar * ptr, gchar ** endptr, gint * val)
162 g_return_val_if_fail (ptr != NULL, FALSE);
163 g_return_val_if_fail (val != NULL, FALSE);
166 ret = g_ascii_strtoll (ptr, &end, 10);
167 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
168 || (errno != 0 && ret == 0)) {
169 GST_WARNING ("%s", g_strerror (errno));
173 if (ret > G_MAXINT || ret < G_MININT) {
174 GST_WARNING ("%s", g_strerror (ERANGE));
187 int64_from_string (gchar * ptr, gchar ** endptr, gint64 * val)
192 g_return_val_if_fail (ptr != NULL, FALSE);
193 g_return_val_if_fail (val != NULL, FALSE);
196 ret = g_ascii_strtoll (ptr, &end, 10);
197 if ((errno == ERANGE && (ret == G_MAXINT64 || ret == G_MININT64))
198 || (errno != 0 && ret == 0)) {
199 GST_WARNING ("%s", g_strerror (errno));
212 double_from_string (gchar * ptr, gchar ** endptr, gdouble * val)
217 g_return_val_if_fail (ptr != NULL, FALSE);
218 g_return_val_if_fail (val != NULL, FALSE);
221 ret = g_ascii_strtod (ptr, &end);
222 if ((errno == ERANGE && (ret == HUGE_VAL || ret == -HUGE_VAL))
223 || (errno != 0 && ret == 0)) {
224 GST_WARNING ("%s", g_strerror (errno));
228 if (!isfinite (ret)) {
229 GST_WARNING ("%s", g_strerror (ERANGE));
236 *val = (gdouble) ret;
242 parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
244 gchar *end = NULL, *p, *ve;
246 g_return_val_if_fail (ptr != NULL, FALSE);
247 g_return_val_if_fail (*ptr != NULL, FALSE);
248 g_return_val_if_fail (a != NULL, FALSE);
249 g_return_val_if_fail (v != NULL, FALSE);
251 /* [attribute=value,]* */
254 end = p = g_utf8_strchr (*ptr, -1, ',');
256 gchar *q = g_utf8_strchr (*ptr, -1, '"');
258 /* special case, such as CODECS="avc1.77.30, mp4a.40.2" */
259 q = g_utf8_next_char (q);
261 q = g_utf8_strchr (q, -1, '"');
264 end = p = g_utf8_strchr (q, -1, ',');
270 end = g_utf8_next_char (end);
271 } while (end && *end == ' ');
275 *v = p = g_utf8_strchr (*ptr, -1, '=');
278 *v = g_utf8_next_char (*v);
280 ve = g_utf8_next_char (*v);
282 ve = g_utf8_strchr (ve, -1, '"');
285 *v = g_utf8_next_char (*v);
288 GST_WARNING ("Cannot remove quotation marks from %s", *a);
292 GST_WARNING ("missing = after attribute");
301 gst_hls_variant_stream_compare_by_bitrate (gconstpointer a, gconstpointer b)
303 const GstHLSVariantStream *vs_a = (const GstHLSVariantStream *) a;
304 const GstHLSVariantStream *vs_b = (const GstHLSVariantStream *) b;
306 if (vs_a->bandwidth == vs_b->bandwidth)
307 return g_strcmp0 (vs_a->name, vs_b->name);
309 return vs_a->bandwidth - vs_b->bandwidth;
313 gst_m3u8_update_check_consistent_media_seqnums (GstM3U8 * self,
314 gboolean have_mediasequence, GList * previous_files)
319 /* If we have MEDIA-SEQUENCE, ensure that it's consistent. If it is not,
320 * the client SHOULD halt playback (6.3.4), which is what we do then.
322 * If we don't have MEDIA-SEQUENCE, we check URIs in the previous and
323 * current playlist to calculate the/a correct MEDIA-SEQUENCE for the new
324 * playlist in relation to the old. That is, same URIs get the same number
325 * and later URIs get higher numbers */
326 if (have_mediasequence) {
328 GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
330 /* Find first case of higher/equal sequence number in new playlist or
331 * same URI. From there on we can linearly step ahead */
332 for (l = self->files; l; l = l->next) {
333 gboolean match = FALSE;
336 for (m = previous_files; m; m = m->next) {
339 if (f1->sequence >= f2->sequence || g_str_equal (f1->uri, f2->uri)) {
349 /* No match, no sequence in the new playlist was higher than
350 * any in the old, and no URI was found again. This is bad! */
351 GST_ERROR ("Media sequences inconsistent, ignoring");
354 g_assert (f1 != NULL);
355 g_assert (f2 != NULL);
357 for (; l && m; l = l->next, m = m->next) {
361 if (f1->sequence == f2->sequence) {
362 if (!g_str_equal (f1->uri, f2->uri)) {
363 /* Same sequence, different URI. This is bad! */
364 GST_ERROR ("Media sequences inconsistent, ignoring");
367 /* Good case, we advance and check the next one */
369 } else if (g_str_equal (f1->uri, f2->uri)) {
370 /* Same URIs but different sequences, this is bad! */
371 GST_ERROR ("Media sequences inconsistent, ignoring");
374 /* Not same URI, not same sequence but by construction sequence
375 * must be higher in the new one. All good in that case, if it
376 * isn't then this means that sequence numbers are decreasing
377 * or files were inserted */
378 if (f1->sequence < f2->sequence) {
379 GST_ERROR ("Media sequences inconsistent, ignoring");
385 /* All good if we're getting here */
389 GstM3U8MediaFile *f1 = NULL, *f2 = NULL;
390 gint64 mediasequence;
392 for (l = self->files; l; l = l->next) {
393 gboolean match = FALSE;
396 for (m = previous_files; m; m = m->next) {
399 if (g_str_equal (f1->uri, f2->uri)) {
410 /* No match, this means f2 is the last item in the previous playlist
411 * and we have to start our new playlist at that sequence */
412 mediasequence = f2->sequence + 1;
414 for (l = self->files; l; l = l->next) {
416 f1->sequence = mediasequence;
420 /* Match, check that all following ones are matching too and continue
421 * sequence numbers from there on */
423 mediasequence = f2->sequence;
425 for (; l; l = l->next) {
427 f2 = m ? m->data : NULL;
429 f1->sequence = mediasequence;
433 if (!g_str_equal (f1->uri, f2->uri)) {
434 GST_WARNING ("Inconsistent URIs after playlist update");
448 * @data: a m3u8 playlist text data, taking ownership
451 gst_m3u8_update (GstM3U8 * self, gchar * data)
454 GstClockTime duration;
456 gboolean discontinuity = FALSE;
457 gchar *current_key = NULL;
458 gboolean have_iv = FALSE;
459 guint8 iv[16] = { 0, };
460 gint64 size = -1, offset = -1;
461 gint64 mediasequence;
462 GList *previous_files = NULL;
463 gboolean have_mediasequence = FALSE;
465 g_return_val_if_fail (self != NULL, FALSE);
466 g_return_val_if_fail (data != NULL, FALSE);
468 GST_M3U8_LOCK (self);
470 /* check if the data changed since last update */
471 if (self->last_data && g_str_equal (self->last_data, data)) {
472 GST_DEBUG ("Playlist is the same as previous one");
474 GST_M3U8_UNLOCK (self);
478 if (!g_str_has_prefix (data, "#EXTM3U")) {
479 GST_WARNING ("Data doesn't start with #EXTM3U");
481 GST_M3U8_UNLOCK (self);
485 if (g_strrstr (data, "\n#EXT-X-STREAM-INF:") != NULL) {
486 GST_WARNING ("Not a media playlist, but a master playlist!");
487 GST_M3U8_UNLOCK (self);
491 GST_TRACE ("data:\n%s", data);
493 g_free (self->last_data);
494 self->last_data = data;
496 self->current_file = NULL;
497 previous_files = self->files;
499 self->duration = GST_CLOCK_TIME_NONE;
502 /* By default, allow caching */
503 self->allowcache = TRUE;
511 end = g_utf8_strchr (data, -1, '\n');
515 r = g_utf8_strchr (data, -1, '\r');
519 if (data[0] != '#' && data[0] != '\0') {
521 GST_LOG ("%s: got line without EXTINF, dropping", data);
525 data = uri_join (self->base_uri ? self->base_uri : self->uri, data);
527 GstM3U8MediaFile *file;
528 file = gst_m3u8_media_file_new (data, title, duration, mediasequence++);
530 /* set encryption params */
531 file->key = current_key ? g_strdup (current_key) : NULL;
534 memcpy (file->iv, iv, sizeof (iv));
536 guint8 *iv = file->iv + 12;
537 GST_WRITE_UINT32_BE (iv, file->sequence);
544 file->offset = offset;
546 GstM3U8MediaFile *prev = self->files ? self->files->data : NULL;
551 offset = prev->offset + prev->size;
553 file->offset = offset;
560 file->discont = discontinuity;
564 discontinuity = FALSE;
566 self->files = g_list_prepend (self->files, file);
569 } else if (g_str_has_prefix (data, "#EXTINF:")) {
571 if (!double_from_string (data + 8, &data, &fval)) {
572 GST_WARNING ("Can't read EXTINF duration");
575 duration = fval * (gdouble) GST_SECOND;
576 if (self->targetduration > 0 && duration > self->targetduration) {
577 GST_WARNING ("EXTINF duration (%" GST_TIME_FORMAT
578 ") > TARGETDURATION (%" GST_TIME_FORMAT ")",
579 GST_TIME_ARGS (duration), GST_TIME_ARGS (self->targetduration));
581 if (!data || *data != ',')
583 data = g_utf8_next_char (data);
586 title = g_strdup (data);
588 } else if (g_str_has_prefix (data, "#EXT-X-")) {
589 gchar *data_ext_x = data + 7;
591 /* All these entries start with #EXT-X- */
592 if (g_str_has_prefix (data_ext_x, "ENDLIST")) {
593 self->endlist = TRUE;
594 } else if (g_str_has_prefix (data_ext_x, "VERSION:")) {
595 if (int_from_string (data + 15, &data, &val))
597 } else if (g_str_has_prefix (data_ext_x, "TARGETDURATION:")) {
598 if (int_from_string (data + 22, &data, &val))
599 self->targetduration = val * GST_SECOND;
600 } else if (g_str_has_prefix (data_ext_x, "MEDIA-SEQUENCE:")) {
601 if (int_from_string (data + 22, &data, &val)) {
603 have_mediasequence = TRUE;
605 } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY-SEQUENCE:")) {
606 if (int_from_string (data + 30, &data, &val)
607 && val != self->discont_sequence) {
608 self->discont_sequence = val;
609 discontinuity = TRUE;
611 } else if (g_str_has_prefix (data_ext_x, "DISCONTINUITY")) {
612 self->discont_sequence++;
613 discontinuity = TRUE;
614 } else if (g_str_has_prefix (data_ext_x, "PROGRAM-DATE-TIME:")) {
615 /* <YYYY-MM-DDThh:mm:ssZ> */
616 GST_DEBUG ("FIXME parse date");
617 } else if (g_str_has_prefix (data_ext_x, "ALLOW-CACHE:")) {
618 self->allowcache = g_ascii_strcasecmp (data + 19, "YES") == 0;
619 } else if (g_str_has_prefix (data_ext_x, "KEY:")) {
624 /* IV and KEY are only valid until the next #EXT-X-KEY */
626 g_free (current_key);
628 while (data && parse_attributes (&data, &a, &v)) {
629 if (g_str_equal (a, "URI")) {
631 uri_join (self->base_uri ? self->base_uri : self->uri, v);
632 } else if (g_str_equal (a, "IV")) {
636 if (strlen (ivp) < 32 + 2 || (!g_str_has_prefix (ivp, "0x")
637 && !g_str_has_prefix (ivp, "0X"))) {
638 GST_WARNING ("Can't read IV");
643 for (i = 0; i < 16; i++) {
646 h = g_ascii_xdigit_value (*ivp);
648 l = g_ascii_xdigit_value (*ivp);
650 if (h == -1 || l == -1) {
654 iv[i] = (h << 4) | l;
658 GST_WARNING ("Can't read IV");
662 } else if (g_str_equal (a, "METHOD")) {
663 if (!g_str_equal (v, "AES-128")) {
664 GST_WARNING ("Encryption method %s not supported", v);
669 } else if (g_str_has_prefix (data_ext_x, "BYTERANGE:")) {
670 gchar *v = data + 17;
672 if (int64_from_string (v, &v, &size)) {
673 if (*v == '@' && !int64_from_string (v + 1, &v, &offset))
679 GST_LOG ("Ignored line: %s", data);
682 GST_LOG ("Ignored line: %s", data);
688 data = g_utf8_next_char (end); /* skip \n */
691 g_free (current_key);
694 self->files = g_list_reverse (self->files);
696 if (previous_files) {
697 gboolean consistent = gst_m3u8_update_check_consistent_media_seqnums (self,
698 have_mediasequence, previous_files);
700 g_list_foreach (previous_files, (GFunc) gst_m3u8_media_file_unref, NULL);
701 g_list_free (previous_files);
702 previous_files = NULL;
704 /* error was reported above already */
706 GST_M3U8_UNLOCK (self);
711 if (self->files == NULL) {
712 GST_ERROR ("Invalid media playlist, it does not contain any media files");
713 GST_M3U8_UNLOCK (self);
717 /* calculate the start and end times of this media playlist. */
720 GstM3U8MediaFile *file;
721 GstClockTime duration = 0;
725 for (walk = self->files; walk; walk = walk->next) {
728 if (mediasequence == -1) {
729 mediasequence = file->sequence;
730 } else if (mediasequence >= file->sequence) {
731 GST_ERROR ("Non-increasing media sequence");
732 GST_M3U8_UNLOCK (self);
735 mediasequence = file->sequence;
738 duration += file->duration;
739 if (file->sequence > self->highest_sequence_number) {
740 if (self->highest_sequence_number >= 0) {
741 /* if an update of the media playlist has been missed, there
742 will be a gap between self->highest_sequence_number and the
743 first sequence number in this media playlist. In this situation
744 assume that the missing fragments had a duration of
745 targetduration each */
746 self->last_file_end +=
747 (file->sequence - self->highest_sequence_number -
748 1) * self->targetduration;
750 self->last_file_end += file->duration;
751 self->highest_sequence_number = file->sequence;
754 if (GST_M3U8_IS_LIVE (self)) {
755 self->first_file_start = self->last_file_end - duration;
756 GST_DEBUG ("Live playlist range %" GST_TIME_FORMAT " -> %"
757 GST_TIME_FORMAT, GST_TIME_ARGS (self->first_file_start),
758 GST_TIME_ARGS (self->last_file_end));
760 self->duration = duration;
763 /* first-time setup */
764 if (self->files && self->sequence == -1) {
767 if (GST_M3U8_IS_LIVE (self)) {
769 GstClockTime sequence_pos = 0;
771 file = g_list_last (self->files);
773 if (self->last_file_end >= GST_M3U8_MEDIA_FILE (file->data)->duration) {
775 self->last_file_end - GST_M3U8_MEDIA_FILE (file->data)->duration;
778 /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
779 * the end of the playlist. See section 6.3.3 of HLS draft */
780 for (i = 0; i < GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE && file->prev &&
781 GST_M3U8_MEDIA_FILE (file->prev->data)->duration <= sequence_pos;
784 sequence_pos -= GST_M3U8_MEDIA_FILE (file->data)->duration;
786 self->sequence_position = sequence_pos;
788 file = g_list_first (self->files);
789 self->sequence_position = 0;
791 self->current_file = file;
792 self->sequence = GST_M3U8_MEDIA_FILE (file->data)->sequence;
793 GST_DEBUG ("first sequence: %u", (guint) self->sequence);
796 GST_LOG ("processed media playlist %s, %u fragments", self->name,
797 g_list_length (self->files));
799 GST_M3U8_UNLOCK (self);
804 /* call with M3U8_LOCK held */
806 m3u8_find_next_fragment (GstM3U8 * m3u8, gboolean forward)
808 GstM3U8MediaFile *file;
809 GList *l = m3u8->files;
815 if (file->sequence >= m3u8->sequence)
826 if (file->sequence <= m3u8->sequence)
837 gst_m3u8_get_next_fragment (GstM3U8 * m3u8, gboolean forward,
838 GstClockTime * sequence_position, gboolean * discont)
840 GstM3U8MediaFile *file = NULL;
842 g_return_val_if_fail (m3u8 != NULL, NULL);
844 GST_M3U8_LOCK (m3u8);
846 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
848 if (m3u8->sequence < 0) /* can't happen really */
851 if (m3u8->current_file == NULL)
852 m3u8->current_file = m3u8_find_next_fragment (m3u8, forward);
854 if (m3u8->current_file == NULL)
857 file = gst_m3u8_media_file_ref (m3u8->current_file->data);
859 GST_DEBUG ("Got fragment with sequence %u (current sequence %u)",
860 (guint) file->sequence, (guint) m3u8->sequence);
862 if (sequence_position)
863 *sequence_position = m3u8->sequence_position;
865 *discont = file->discont || (m3u8->sequence != file->sequence);
867 m3u8->current_file_duration = file->duration;
868 m3u8->sequence = file->sequence;
872 GST_M3U8_UNLOCK (m3u8);
878 gst_m3u8_has_next_fragment (GstM3U8 * m3u8, gboolean forward)
883 g_return_val_if_fail (m3u8 != NULL, FALSE);
885 GST_M3U8_LOCK (m3u8);
887 GST_DEBUG ("Checking next fragment %" G_GINT64_FORMAT,
888 m3u8->sequence + (forward ? 1 : -1));
890 if (m3u8->current_file) {
891 cur = m3u8->current_file;
893 cur = m3u8_find_next_fragment (m3u8, forward);
896 have_next = cur && ((forward && cur->next) || (!forward && cur->prev));
898 GST_M3U8_UNLOCK (m3u8);
903 /* call with M3U8_LOCK held */
905 m3u8_alternate_advance (GstM3U8 * m3u8, gboolean forward)
907 gint targetnum = m3u8->sequence;
909 GstM3U8MediaFile *mf;
911 /* figure out the target seqnum */
917 for (tmp = m3u8->files; tmp; tmp = tmp->next) {
918 mf = (GstM3U8MediaFile *) tmp->data;
919 if (mf->sequence == targetnum)
923 GST_WARNING ("Can't find next fragment");
926 m3u8->current_file = tmp;
927 m3u8->sequence = targetnum;
928 m3u8->current_file_duration = GST_M3U8_MEDIA_FILE (tmp->data)->duration;
932 gst_m3u8_advance_fragment (GstM3U8 * m3u8, gboolean forward)
934 GstM3U8MediaFile *file;
936 g_return_if_fail (m3u8 != NULL);
938 GST_M3U8_LOCK (m3u8);
940 GST_DEBUG ("Sequence position was %" GST_TIME_FORMAT,
941 GST_TIME_ARGS (m3u8->sequence_position));
942 if (GST_CLOCK_TIME_IS_VALID (m3u8->current_file_duration)) {
943 /* Advance our position based on the previous fragment we played */
945 m3u8->sequence_position += m3u8->current_file_duration;
946 else if (m3u8->current_file_duration < m3u8->sequence_position)
947 m3u8->sequence_position -= m3u8->current_file_duration;
949 m3u8->sequence_position = 0;
950 m3u8->current_file_duration = GST_CLOCK_TIME_NONE;
951 GST_DEBUG ("Sequence position now %" GST_TIME_FORMAT,
952 GST_TIME_ARGS (m3u8->sequence_position));
954 if (!m3u8->current_file) {
957 GST_DEBUG ("Looking for fragment %" G_GINT64_FORMAT, m3u8->sequence);
958 for (l = m3u8->files; l != NULL; l = l->next) {
959 if (GST_M3U8_MEDIA_FILE (l->data)->sequence == m3u8->sequence) {
960 m3u8->current_file = l;
964 if (m3u8->current_file == NULL) {
966 ("Could not find current fragment, trying next fragment directly");
967 m3u8_alternate_advance (m3u8, forward);
969 /* Resync sequence number if the above has failed for live streams */
970 if (m3u8->current_file == NULL && GST_M3U8_IS_LIVE (m3u8)) {
971 /* for live streams, start GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE from
972 the end of the playlist. See section 6.3.3 of HLS draft */
974 g_list_length (m3u8->files) - GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
975 m3u8->current_file = g_list_nth (m3u8->files, pos >= 0 ? pos : 0);
976 m3u8->current_file_duration =
977 GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
979 GST_WARNING ("Resyncing live playlist");
985 file = GST_M3U8_MEDIA_FILE (m3u8->current_file->data);
986 GST_DEBUG ("Advancing from sequence %u", (guint) file->sequence);
988 m3u8->current_file = m3u8->current_file->next;
989 if (m3u8->current_file) {
990 m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
992 m3u8->sequence = file->sequence + 1;
995 m3u8->current_file = m3u8->current_file->prev;
996 if (m3u8->current_file) {
997 m3u8->sequence = GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->sequence;
999 m3u8->sequence = file->sequence - 1;
1002 if (m3u8->current_file) {
1003 /* Store duration of the fragment we're using to update the position
1004 * the next time we advance */
1005 m3u8->current_file_duration =
1006 GST_M3U8_MEDIA_FILE (m3u8->current_file->data)->duration;
1011 GST_M3U8_UNLOCK (m3u8);
1015 gst_m3u8_get_duration (GstM3U8 * m3u8)
1017 GstClockTime duration = GST_CLOCK_TIME_NONE;
1019 g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1021 GST_M3U8_LOCK (m3u8);
1023 /* We can only get the duration for on-demand streams */
1027 if (!GST_CLOCK_TIME_IS_VALID (m3u8->duration) && m3u8->files != NULL) {
1031 for (f = m3u8->files; f != NULL; f = f->next)
1032 m3u8->duration += GST_M3U8_MEDIA_FILE (f)->duration;
1034 duration = m3u8->duration;
1038 GST_M3U8_UNLOCK (m3u8);
1044 gst_m3u8_get_target_duration (GstM3U8 * m3u8)
1046 GstClockTime target_duration;
1048 g_return_val_if_fail (m3u8 != NULL, GST_CLOCK_TIME_NONE);
1050 GST_M3U8_LOCK (m3u8);
1051 target_duration = m3u8->targetduration;
1052 GST_M3U8_UNLOCK (m3u8);
1054 return target_duration;
1058 gst_m3u8_get_uri (GstM3U8 * m3u8)
1062 GST_M3U8_LOCK (m3u8);
1063 uri = g_strdup (m3u8->uri);
1064 GST_M3U8_UNLOCK (m3u8);
1070 gst_m3u8_is_live (GstM3U8 * m3u8)
1074 g_return_val_if_fail (m3u8 != NULL, FALSE);
1076 GST_M3U8_LOCK (m3u8);
1077 is_live = GST_M3U8_IS_LIVE (m3u8);
1078 GST_M3U8_UNLOCK (m3u8);
1084 uri_join (const gchar * uri1, const gchar * uri2)
1086 gchar *uri_copy, *tmp, *ret = NULL;
1088 if (gst_uri_is_valid (uri2))
1089 return g_strdup (uri2);
1091 uri_copy = g_strdup (uri1);
1092 if (uri2[0] != '/') {
1093 /* uri2 is a relative uri2 */
1094 /* look for query params */
1095 tmp = g_utf8_strchr (uri_copy, -1, '?');
1097 /* find last / char, ignoring query params */
1098 tmp = g_utf8_strrchr (uri_copy, tmp - uri_copy, '/');
1100 /* find last / char in URL */
1101 tmp = g_utf8_strrchr (uri_copy, -1, '/');
1104 GST_WARNING ("Can't build a valid uri_copy");
1109 ret = g_strdup_printf ("%s/%s", uri_copy, uri2);
1111 /* uri2 is an absolute uri2 */
1112 char *scheme, *hostname;
1115 /* find the : in <scheme>:// */
1116 tmp = g_utf8_strchr (uri_copy, -1, ':');
1118 GST_WARNING ("Can't build a valid uri_copy");
1127 tmp = g_utf8_strchr (hostname, -1, '/');
1131 ret = g_strdup_printf ("%s://%s%s", scheme, hostname, uri2);
1140 gst_m3u8_get_seek_range (GstM3U8 * m3u8, gint64 * start, gint64 * stop)
1142 GstClockTime duration = 0;
1144 GstM3U8MediaFile *file;
1146 guint min_distance = 0;
1148 g_return_val_if_fail (m3u8 != NULL, FALSE);
1150 GST_M3U8_LOCK (m3u8);
1152 if (m3u8->files == NULL)
1155 if (GST_M3U8_IS_LIVE (m3u8)) {
1156 /* min_distance is used to make sure the seek range is never closer than
1157 GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE fragments from the end of a live
1158 playlist - see 6.3.3. "Playing the Playlist file" of the HLS draft */
1159 min_distance = GST_M3U8_LIVE_MIN_FRAGMENT_DISTANCE;
1161 count = g_list_length (m3u8->files);
1163 for (walk = m3u8->files; walk && count > min_distance; walk = walk->next) {
1166 duration += file->duration;
1172 *start = m3u8->first_file_start;
1173 *stop = *start + duration;
1177 GST_M3U8_UNLOCK (m3u8);
1178 return (duration > 0);
1182 gst_hls_media_ref (GstHLSMedia * media)
1184 g_assert (media != NULL && media->ref_count > 0);
1185 g_atomic_int_add (&media->ref_count, 1);
1190 gst_hls_media_unref (GstHLSMedia * media)
1192 g_assert (media != NULL && media->ref_count > 0);
1193 if (g_atomic_int_dec_and_test (&media->ref_count)) {
1194 if (media->playlist)
1195 gst_m3u8_unref (media->playlist);
1196 g_free (media->group_id);
1197 g_free (media->name);
1198 g_free (media->uri);
1199 g_free (media->lang);
1204 static GstHLSMediaType
1205 gst_m3u8_get_hls_media_type_from_string (const gchar * type_name)
1207 if (strcmp (type_name, "AUDIO") == 0)
1208 return GST_HLS_MEDIA_TYPE_AUDIO;
1209 if (strcmp (type_name, "VIDEO") == 0)
1210 return GST_HLS_MEDIA_TYPE_VIDEO;
1211 if (strcmp (type_name, "SUBTITLES") == 0)
1212 return GST_HLS_MEDIA_TYPE_SUBTITLES;
1213 if (strcmp (type_name, "CLOSED_CAPTIONS") == 0)
1214 return GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS;
1216 return GST_HLS_MEDIA_TYPE_INVALID;
1219 #define GST_HLS_MEDIA_TYPE_NAME(mtype) gst_m3u8_hls_media_type_get_nick(mtype)
1220 static inline const gchar *
1221 gst_m3u8_hls_media_type_get_nick (GstHLSMediaType mtype)
1223 static const gchar *nicks[GST_HLS_N_MEDIA_TYPES] = { "audio", "video",
1224 "subtitle", "closed-captions"
1227 if (mtype < 0 || mtype >= GST_HLS_N_MEDIA_TYPES)
1230 return nicks[mtype];
1233 /* returns unquoted copy of string */
1235 gst_m3u8_unquote (const gchar * str)
1237 const gchar *start, *end;
1239 start = strchr (str, '"');
1241 return g_strdup (str);
1242 end = strchr (start + 1, '"');
1244 GST_WARNING ("Broken quoted string [%s] - can't find end quote", str);
1245 return g_strdup (start + 1);
1247 return g_strndup (start + 1, (gsize) (end - (start + 1)));
1250 static GstHLSMedia *
1251 gst_m3u8_parse_media (gchar * desc, const gchar * base_uri)
1256 media = g_new0 (GstHLSMedia, 1);
1257 media->ref_count = 1;
1258 media->playlist = gst_m3u8_new ();
1259 media->mtype = GST_HLS_MEDIA_TYPE_INVALID;
1261 GST_LOG ("parsing %s", desc);
1262 while (desc != NULL && parse_attributes (&desc, &a, &v)) {
1263 if (strcmp (a, "TYPE") == 0) {
1264 media->mtype = gst_m3u8_get_hls_media_type_from_string (v);
1265 } else if (strcmp (a, "GROUP-ID") == 0) {
1266 g_free (media->group_id);
1267 media->group_id = gst_m3u8_unquote (v);
1268 } else if (strcmp (a, "NAME") == 0) {
1269 g_free (media->name);
1270 media->name = gst_m3u8_unquote (v);
1271 } else if (strcmp (a, "URI") == 0) {
1274 g_free (media->uri);
1275 uri = gst_m3u8_unquote (v);
1276 media->uri = uri_join (base_uri, uri);
1278 } else if (strcmp (a, "LANGUAGE") == 0) {
1279 g_free (media->lang);
1280 media->lang = gst_m3u8_unquote (v);
1281 } else if (strcmp (a, "DEFAULT") == 0) {
1282 media->is_default = g_ascii_strcasecmp (v, "yes") == 0;
1283 } else if (strcmp (a, "FORCED") == 0) {
1284 media->forced = g_ascii_strcasecmp (v, "yes") == 0;
1285 } else if (strcmp (a, "AUTOSELECT") == 0) {
1286 media->autoselect = g_ascii_strcasecmp (v, "yes") == 0;
1288 /* unhandled: ASSOC-LANGUAGE, INSTREAM-ID, CHARACTERISTICS */
1289 GST_FIXME ("EXT-X-MEDIA: unhandled attribute: %s = %s", a, v);
1293 if (media->mtype == GST_HLS_MEDIA_TYPE_INVALID)
1294 goto required_attributes_missing;
1296 if (media->uri == NULL)
1297 goto existing_stream;
1299 if (media->group_id == NULL || media->name == NULL)
1300 goto required_attributes_missing;
1302 if (media->mtype == GST_HLS_MEDIA_TYPE_CLOSED_CAPTIONS)
1305 GST_DEBUG ("media: %s, group '%s', name '%s', uri '%s', %s %s %s, lang=%s",
1306 GST_HLS_MEDIA_TYPE_NAME (media->mtype), media->group_id, media->name,
1307 media->uri, media->is_default ? "default" : "-",
1308 media->autoselect ? "autoselect" : "-",
1309 media->forced ? "forced" : "-", media->lang ? media->lang : "??");
1315 GST_WARNING ("closed captions EXT-X-MEDIA should not have URI specified");
1318 required_attributes_missing:
1320 GST_WARNING ("EXT-X-MEDIA description is missing required attributes");
1326 GST_DEBUG ("EXT-X-MEDIA without URI, describes embedded stream, skipping");
1332 gst_hls_media_unref (media);
1337 static GstHLSVariantStream *
1338 gst_hls_variant_stream_new (void)
1340 GstHLSVariantStream *stream;
1342 stream = g_new0 (GstHLSVariantStream, 1);
1343 stream->m3u8 = gst_m3u8_new ();
1344 stream->refcount = 1;
1348 GstHLSVariantStream *
1349 gst_hls_variant_stream_ref (GstHLSVariantStream * stream)
1351 g_atomic_int_inc (&stream->refcount);
1356 gst_hls_variant_stream_unref (GstHLSVariantStream * stream)
1358 if (g_atomic_int_dec_and_test (&stream->refcount)) {
1361 g_free (stream->name);
1362 g_free (stream->uri);
1363 g_free (stream->codecs);
1364 gst_m3u8_unref (stream->m3u8);
1365 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1366 g_free (stream->media_groups[i]);
1367 g_list_free_full (stream->media[i], (GDestroyNotify) gst_hls_media_unref);
1373 static GstHLSVariantStream *
1374 find_variant_stream_by_name (GList * list, const gchar * name)
1376 for (; list != NULL; list = list->next) {
1377 GstHLSVariantStream *variant_stream = list->data;
1379 if (variant_stream->name != NULL && !strcmp (variant_stream->name, name))
1380 return variant_stream;
1385 static GstHLSVariantStream *
1386 find_variant_stream_by_uri (GList * list, const gchar * uri)
1388 for (; list != NULL; list = list->next) {
1389 GstHLSVariantStream *variant_stream = list->data;
1391 if (variant_stream->uri != NULL && !strcmp (variant_stream->uri, uri))
1392 return variant_stream;
1397 static GstHLSMasterPlaylist *
1398 gst_hls_master_playlist_new (void)
1400 GstHLSMasterPlaylist *playlist;
1402 playlist = g_new0 (GstHLSMasterPlaylist, 1);
1403 playlist->refcount = 1;
1404 playlist->is_simple = FALSE;
1410 gst_hls_master_playlist_unref (GstHLSMasterPlaylist * playlist)
1412 if (g_atomic_int_dec_and_test (&playlist->refcount)) {
1413 g_list_free_full (playlist->variants,
1414 (GDestroyNotify) gst_hls_variant_stream_unref);
1415 g_list_free_full (playlist->iframe_variants,
1416 (GDestroyNotify) gst_hls_variant_stream_unref);
1417 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
1418 g_list_free_full (playlist->variant_info, g_free);
1420 if (playlist->default_variant)
1421 gst_hls_variant_stream_unref (playlist->default_variant);
1422 g_free (playlist->last_data);
1428 hls_media_name_compare_func (gconstpointer media, gconstpointer name)
1430 return strcmp (((GstHLSMedia *) media)->name, (const gchar *) name);
1433 /* Takes ownership of @data */
1434 GstHLSMasterPlaylist *
1435 gst_hls_master_playlist_new_from_data (gchar * data, const gchar * base_uri)
1437 GHashTable *media_groups[GST_HLS_N_MEDIA_TYPES] = { NULL, };
1438 GstHLSMasterPlaylist *playlist;
1439 GstHLSVariantStream *pending_stream;
1440 gchar *end, *free_data = data;
1444 if (!g_str_has_prefix (data, "#EXTM3U")) {
1445 GST_WARNING ("Data doesn't start with #EXTM3U");
1450 playlist = gst_hls_master_playlist_new ();
1452 /* store data before we modify it for parsing */
1453 playlist->last_data = g_strdup (data);
1455 GST_TRACE ("data:\n%s", data);
1457 if (strstr (data, "\n#EXTINF:") != NULL) {
1458 GST_INFO ("This is a simple media playlist, not a master playlist");
1460 pending_stream = gst_hls_variant_stream_new ();
1461 pending_stream->name = g_strdup (base_uri);
1462 pending_stream->uri = g_strdup (base_uri);
1463 gst_m3u8_set_uri (pending_stream->m3u8, base_uri, NULL, base_uri);
1464 playlist->variants = g_list_append (playlist->variants, pending_stream);
1465 playlist->default_variant = gst_hls_variant_stream_ref (pending_stream);
1466 playlist->is_simple = TRUE;
1468 if (!gst_m3u8_update (pending_stream->m3u8, data)) {
1469 GST_WARNING ("Failed to parse media playlist");
1470 gst_hls_master_playlist_unref (playlist);
1476 pending_stream = NULL;
1481 end = g_utf8_strchr (data, -1, '\n');
1485 r = g_utf8_strchr (data, -1, '\r');
1489 if (data[0] != '#' && data[0] != '\0') {
1492 if (pending_stream == NULL) {
1493 GST_LOG ("%s: got line without EXT-STREAM-INF, dropping", data);
1498 uri = uri_join (base_uri, name);
1502 pending_stream->name = g_strdup (name);
1503 pending_stream->uri = uri;
1505 if (find_variant_stream_by_name (playlist->variants, name)
1506 || find_variant_stream_by_uri (playlist->variants, uri)) {
1507 GST_DEBUG ("Already have a list with this name or URI: %s", name);
1508 gst_hls_variant_stream_unref (pending_stream);
1510 GST_INFO ("stream %s @ %u: %s", name, pending_stream->bandwidth, uri);
1511 gst_m3u8_set_uri (pending_stream->m3u8, uri, NULL, name);
1512 playlist->variants = g_list_append (playlist->variants, pending_stream);
1513 /* use first stream in the playlist as default */
1514 if (playlist->default_variant == NULL) {
1515 playlist->default_variant =
1516 gst_hls_variant_stream_ref (pending_stream);
1519 pending_stream = NULL;
1520 } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
1521 if (int_from_string (data + 15, &data, &val))
1522 playlist->version = val;
1523 } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:") ||
1524 g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:")) {
1525 GstHLSVariantStream *stream;
1528 stream = gst_hls_variant_stream_new ();
1529 stream->iframe = g_str_has_prefix (data, "#EXT-X-I-FRAME-STREAM-INF:");
1530 data += stream->iframe ? 26 : 18;
1531 while (data && parse_attributes (&data, &a, &v)) {
1532 if (g_str_equal (a, "BANDWIDTH")) {
1533 if (!int_from_string (v, NULL, &stream->bandwidth))
1534 GST_WARNING ("Error while reading BANDWIDTH");
1535 } else if (g_str_equal (a, "PROGRAM-ID")) {
1536 if (!int_from_string (v, NULL, &stream->program_id))
1537 GST_WARNING ("Error while reading PROGRAM-ID");
1538 } else if (g_str_equal (a, "CODECS")) {
1539 g_free (stream->codecs);
1540 stream->codecs = g_strdup (v);
1541 } else if (g_str_equal (a, "RESOLUTION")) {
1542 if (!int_from_string (v, &v, &stream->width))
1543 GST_WARNING ("Error while reading RESOLUTION width");
1544 if (!v || *v != 'x') {
1545 GST_WARNING ("Missing height");
1547 v = g_utf8_next_char (v);
1548 if (!int_from_string (v, NULL, &stream->height))
1549 GST_WARNING ("Error while reading RESOLUTION height");
1551 } else if (stream->iframe && g_str_equal (a, "URI")) {
1552 stream->uri = uri_join (base_uri, v);
1553 if (stream->uri != NULL) {
1554 stream->name = g_strdup (stream->uri);
1555 gst_m3u8_set_uri (stream->m3u8, stream->uri, NULL, stream->name);
1557 gst_hls_variant_stream_unref (stream);
1559 } else if (g_str_equal (a, "AUDIO")) {
1560 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO]);
1561 stream->media_groups[GST_HLS_MEDIA_TYPE_AUDIO] = gst_m3u8_unquote (v);
1562 } else if (g_str_equal (a, "SUBTITLES")) {
1563 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES]);
1564 stream->media_groups[GST_HLS_MEDIA_TYPE_SUBTITLES] =
1565 gst_m3u8_unquote (v);
1566 } else if (g_str_equal (a, "VIDEO")) {
1567 g_free (stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO]);
1568 stream->media_groups[GST_HLS_MEDIA_TYPE_VIDEO] = gst_m3u8_unquote (v);
1569 } else if (g_str_equal (a, "CLOSED-CAPTIONS")) {
1570 /* closed captions will be embedded inside the video stream, ignore */
1574 if (stream->iframe) {
1575 if (find_variant_stream_by_uri (playlist->iframe_variants, stream->uri)) {
1576 GST_DEBUG ("Already have a list with this URI");
1577 gst_hls_variant_stream_unref (stream);
1579 playlist->iframe_variants =
1580 g_list_append (playlist->iframe_variants, stream);
1583 if (pending_stream != NULL) {
1584 GST_WARNING ("variant stream without uri, dropping");
1585 gst_hls_variant_stream_unref (pending_stream);
1587 pending_stream = stream;
1589 } else if (g_str_has_prefix (data, "#EXT-X-MEDIA:")) {
1593 media = gst_m3u8_parse_media (data + strlen ("#EXT-X-MEDIA:"), base_uri);
1598 if (media_groups[media->mtype] == NULL) {
1599 media_groups[media->mtype] =
1600 g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1603 list = g_hash_table_lookup (media_groups[media->mtype], media->group_id);
1605 /* make sure there isn't already a media with the same name */
1606 if (!g_list_find_custom (list, media->name, hls_media_name_compare_func)) {
1607 g_hash_table_replace (media_groups[media->mtype],
1608 g_strdup (media->group_id), g_list_append (list, media));
1609 GST_INFO ("Added media %s to group %s", media->name, media->group_id);
1611 GST_WARNING (" media with name '%s' already exists in group '%s'!",
1612 media->name, media->group_id);
1613 gst_hls_media_unref (media);
1615 } else if (*data != '\0') {
1616 GST_LOG ("Ignored line: %s", data);
1622 data = g_utf8_next_char (end); /* skip \n */
1625 if (pending_stream != NULL) {
1626 GST_WARNING ("#EXT-X-STREAM-INF without uri, dropping");
1627 gst_hls_variant_stream_unref (pending_stream);
1632 /* Add alternative renditions media to variant streams */
1633 for (l = playlist->variants; l != NULL; l = l->next) {
1634 GstHLSVariantStream *stream = l->data;
1637 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1638 if (stream->media_groups[i] != NULL && media_groups[i] != NULL) {
1639 GST_INFO ("Adding %s group '%s' to stream '%s'",
1640 GST_HLS_MEDIA_TYPE_NAME (i), stream->media_groups[i], stream->name);
1642 mlist = g_hash_table_lookup (media_groups[i], stream->media_groups[i]);
1645 GST_WARNING ("Group '%s' does not exist!", stream->media_groups[i]);
1647 while (mlist != NULL) {
1648 GstHLSMedia *media = mlist->data;
1650 GST_DEBUG (" %s media %s, uri: %s", GST_HLS_MEDIA_TYPE_NAME (i),
1651 media->name, media->uri);
1654 g_list_append (stream->media[i], gst_hls_media_ref (media));
1655 mlist = mlist->next;
1661 /* clean up our temporary alternative rendition groups hash tables */
1662 for (i = 0; i < GST_HLS_N_MEDIA_TYPES; ++i) {
1663 if (media_groups[i] != NULL) {
1664 GList *groups, *mlist;
1666 groups = g_hash_table_get_keys (media_groups[i]);
1667 for (l = groups; l != NULL; l = l->next) {
1668 mlist = g_hash_table_lookup (media_groups[i], l->data);
1669 g_list_free_full (mlist, (GDestroyNotify) gst_hls_media_unref);
1671 g_list_free (groups);
1672 g_hash_table_unref (media_groups[i]);
1676 if (playlist->variants == NULL) {
1677 GST_WARNING ("Master playlist without any media playlists!");
1678 gst_hls_master_playlist_unref (playlist);
1682 /* reorder variants by bitrate */
1683 playlist->variants =
1684 g_list_sort (playlist->variants,
1685 (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1687 playlist->iframe_variants =
1688 g_list_sort (playlist->iframe_variants,
1689 (GCompareFunc) gst_hls_variant_stream_compare_by_bitrate);
1691 /* FIXME: restore old current_variant after master playlist update
1692 * (move into code that does that update) */
1695 gchar *top_variant_uri = NULL;
1696 gboolean iframe = FALSE;
1698 if (!self->current_variant) {
1699 top_variant_uri = GST_M3U8 (self->lists->data)->uri;
1701 top_variant_uri = GST_M3U8 (self->current_variant->data)->uri;
1702 iframe = GST_M3U8 (self->current_variant->data)->iframe;
1705 /* here we sorted the lists */
1708 playlist->current_variant =
1709 find_variant_stream_by_uri (playlist->iframe_variants,
1712 playlist->current_variant =
1713 find_variant_stream_by_uri (playlist->variants, top_variant_uri);
1717 GST_DEBUG ("parsed master playlist with %d streams and %d I-frame streams",
1718 g_list_length (playlist->variants),
1719 g_list_length (playlist->iframe_variants));
1721 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
1722 GList *v = (playlist->iframe_variants)?(playlist->iframe_variants):(playlist->variants);
1724 /* update variant stream info */
1725 for (; v != NULL; v = v->next) {
1726 GstHLSVariantStream *data = v->data;
1727 GstM3U8VideoVariantInfo *var_info = g_new0 (GstM3U8VideoVariantInfo, 1);
1729 GST_LOG ("variant info %d, %d x %d", data->bandwidth, data->width,
1731 var_info->bandwidth = data->bandwidth;
1732 var_info->width = data->width;
1733 var_info->height = data->height;
1735 playlist->variant_info = g_list_append (playlist->variant_info, var_info);
1743 gst_hls_variant_stream_is_live (GstHLSVariantStream * variant)
1747 g_return_val_if_fail (variant != NULL, FALSE);
1749 is_live = gst_m3u8_is_live (variant->m3u8);
1755 compare_media (const GstHLSMedia * a, const GstHLSMedia * b)
1757 return strcmp (a->name, b->name);
1761 gst_hls_variant_find_matching_media (GstHLSVariantStream * stream,
1762 GstHLSMedia * media)
1764 GList *mlist = stream->media[media->mtype];
1770 match = g_list_find_custom (mlist, media, (GCompareFunc) compare_media);
1777 #ifdef TIZEN_FEATURE_ADAPTIVE_MODIFICATION
1779 get_num_of_codec(GstHLSVariantStream * variant)
1781 #define MAX_NUM_OF_CODEC 10
1784 gchar** codec_list = NULL;
1786 if (!variant || !variant->codecs)
1789 codec_list = g_strsplit(variant->codecs, ",", MAX_NUM_OF_CODEC);
1791 cnt = g_strv_length(codec_list);
1792 g_strfreev(codec_list);
1799 check_num_of_codec(GstHLSVariantStream * variant, guint req_num)
1801 guint num_of_codec = 0;
1806 num_of_codec = get_num_of_codec(variant);
1808 if (num_of_codec > 0 && req_num > 0 && num_of_codec != req_num) {
1809 GST_WARNING("can not support to change codec");
1816 GstHLSVariantStream *
1817 gst_hls_master_playlist_get_variant_for_max_limit (GstHLSMasterPlaylist *
1818 playlist, GstHLSVariantStream * current_variant, guint bitrate, gint bandwidth, gint width, gint height)
1820 GstHLSVariantStream *tmp = current_variant;
1821 GstHLSVariantStream *variant = NULL;
1823 guint max_bandwidth = (bandwidth > DEFAULT_ADAPTIVE_VARIANT)?(guint)(bandwidth):(bitrate);
1824 guint num_of_codec = 0;
1826 num_of_codec = get_num_of_codec(current_variant);
1828 GST_INFO("max limit : %u, %d, [w]%d [h]%d", bitrate, bandwidth, width, height);
1830 /* variant lists are sorted low to high, so iterate from highest to lowest */
1831 if (current_variant == NULL || !current_variant->iframe)
1832 l = g_list_last (playlist->variants); /* highest */
1834 l = g_list_last (playlist->iframe_variants);
1836 for (; l; l = g_list_previous(l)) {
1837 if (!check_num_of_codec((GstHLSVariantStream *)l->data, num_of_codec))
1842 if (tmp->bandwidth > max_bandwidth)
1845 if (((width > DEFAULT_ADAPTIVE_VARIANT) && (tmp->width > width)) ||
1846 ((height > DEFAULT_ADAPTIVE_VARIANT) && (tmp->height > height))) {
1847 if (!variant) /* will be kept with the last one with the same bitrate */
1851 GST_INFO("found %d, %d x %d", variant->bandwidth, variant->width, variant->height);
1856 return (variant)?(variant):(tmp);
1860 GstHLSVariantStream *
1861 gst_hls_master_playlist_get_variant_for_bitrate (GstHLSMasterPlaylist *
1862 playlist, GstHLSVariantStream * current_variant, guint bitrate)
1864 GstHLSVariantStream *variant = current_variant;
1867 /* variant lists are sorted low to high, so iterate from highest to lowest */
1868 if (current_variant == NULL || !current_variant->iframe)
1869 l = g_list_last (playlist->variants); /* highest */
1871 l = g_list_last (playlist->iframe_variants);
1875 if (variant->bandwidth <= bitrate)
1884 GstHLSVariantStream *
1885 gst_hls_master_playlist_get_matching_variant (GstHLSMasterPlaylist * playlist,
1886 GstHLSVariantStream * current_variant)
1888 if (current_variant->iframe) {
1889 return find_variant_stream_by_uri (playlist->iframe_variants,
1890 current_variant->uri);
1893 return find_variant_stream_by_uri (playlist->variants, current_variant->uri);