LCOV - code coverage report
Current view: top level - media_tools - m3u8.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 526 660 79.7 %
Date: 2021-04-29 23:48:07 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /**
       2             :  *                      GPAC - Multimedia Framework C SDK
       3             :  *
       4             :  *                      Authors: Pierre Souchay, Jean Le Feuvre, Romain Bouqueau
       5             :  *                      Copyright (c) Telecom ParisTech 2010-2021
       6             :  *                                      All rights reserved
       7             :  *
       8             :  *  This file is part of GPAC
       9             :  *
      10             :  *  GPAC is free software; you can redistribute it and/or modify
      11             :  *  it under the terms of the GNU Lesser General Public License as published by
      12             :  *  the Free Software Foundation; either version 2, or (at your option)
      13             :  *  any later version.
      14             :  *
      15             :  *  GPAC is distributed in the hope that it will be useful,
      16             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18             :  *  GNU Lesser General Public License for more details.
      19             :  *
      20             :  *  You should have received a copy of the GNU Lesser General Public
      21             :  *  License along with this library; see the file COPYING.  If not, write to
      22             :  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
      23             :  *
      24             :  */
      25             : 
      26             : #define _GNU_SOURCE
      27             : 
      28             : #include <gpac/internal/m3u8.h>
      29             : #include <gpac/network.h>
      30             : 
      31             : /********** accumulated_attributes **********/
      32             : 
      33             : typedef struct _s_accumulated_attributes {
      34             :         //TODO: store as a structure with: { attribute, version, mandatory }
      35             :         char *title;
      36             :         char *mediaURL;
      37             :         double duration_in_seconds;
      38             :         int bandwidth;
      39             :         int width, height;
      40             :         int stream_id;
      41             :         char *codecs;
      42             :         char *language;
      43             :         MediaType type;
      44             :         union {
      45             :                 char *audio;
      46             :                 char *video;
      47             :                 char *subtitle;
      48             :                 char *closed_captions;
      49             :         } group;
      50             :         int target_duration_in_seconds;
      51             :         int min_media_sequence;
      52             :         int current_media_seq;
      53             :         int version;
      54             :         int compatibility_version; /*compute version required by the M3U8 content*/
      55             :         Bool is_master_playlist;
      56             :         Bool is_media_segment;
      57             :         Bool is_playlist_ended;
      58             :         u64 playlist_utc_timestamp;
      59             :         u64 byte_range_start, byte_range_end;
      60             :         u64 init_byte_range_start, init_byte_range_end;
      61             :         PlaylistElementDRMMethod key_method;
      62             :         char *init_url;
      63             :         char *key_url;
      64             :         bin128 key_iv;
      65             :         Bool independent_segments;
      66             :         Bool low_latency, independent_part;
      67             :         u32 discontinuity;
      68             : } s_accumulated_attributes;
      69             : 
      70             : 
      71             : /********** playlist_element **********/
      72             : 
      73             : GF_Err playlist_element_del(PlaylistElement * e);
      74             : 
      75         155 : static GF_Err cleanup_list_of_elements(GF_List *list) {
      76             :         GF_Err result = GF_OK;
      77         155 :         if (list == NULL)
      78             :                 return result;
      79        1768 :         while (gf_list_count(list)) {
      80        1613 :                 PlaylistElement *pl = (PlaylistElement *) gf_list_get(list, 0);
      81        1613 :                 if (pl)
      82        1613 :                         result |= playlist_element_del(pl);
      83        1613 :                 gf_list_rem(list, 0);
      84             :         }
      85         155 :         gf_list_del(list);
      86         155 :         return result;
      87             : }
      88             : 
      89             : /**
      90             :  * Deletes an Playlist element
      91             :  */
      92        1768 : GF_Err playlist_element_del(PlaylistElement * e) {
      93             :         GF_Err result = GF_OK;
      94        1768 :         if (e == NULL)
      95             :                 return result;
      96        1768 :         if (e->title) {
      97           0 :                 gf_free(e->title);
      98             :         }
      99        1768 :         if (e->codecs) {
     100          42 :                 gf_free(e->codecs);
     101             :         }
     102        1768 :         if (e->language) {
     103          13 :                 gf_free(e->language);
     104             :         }
     105        1768 :         if (e->audio_group) {
     106           0 :                 gf_free(e->audio_group);
     107             :         }
     108        1768 :         if (e->video_group) {
     109           0 :                 gf_free(e->video_group);
     110             :         }
     111        1768 :         if (e->key_uri) {
     112           9 :                 gf_free(e->key_uri);
     113             :         }
     114        1768 :         if (e->init_segment_url) {
     115         444 :                 gf_free(e->init_segment_url);
     116             :         }
     117        1768 :         memset(e->key_iv, 0, sizeof(bin128) );
     118        1768 :         if (e->url) 
     119        1716 :                 gf_free(e->url);
     120             : 
     121        1768 :         switch (e->element_type) {
     122             :         case TYPE_UNKNOWN:
     123             :         case TYPE_MEDIA:
     124             :                 break;
     125         155 :         case TYPE_PLAYLIST:
     126             :                 assert(e->element.playlist.elements);
     127         155 :                 result |= cleanup_list_of_elements(e->element.playlist.elements);
     128             :         default:
     129             :                 break;
     130             :         }
     131        1768 :         gf_free(e);
     132        1768 :         return result;
     133             : }
     134             : 
     135             : /**
     136             :  * Creates an Playlist element.
     137             :  * This element can be either a playlist of a stream according to first parameter.
     138             : \param NULL if element could not be created. Elements will be deleted recursively.
     139             :  */
     140        1768 : static PlaylistElement* playlist_element_new(PlaylistElementType element_type, const char *url, s_accumulated_attributes *attribs)
     141             : {
     142             :         PlaylistElement *e;
     143        1768 :         GF_SAFEALLOC(e, PlaylistElement);
     144        1768 :         if (e == NULL)
     145             :                 return NULL;
     146             : 
     147        1768 :         e->media_type = attribs->type;
     148        1768 :         e->duration_info = attribs->duration_in_seconds;
     149        1768 :         e->byte_range_start = attribs->byte_range_start;
     150        1768 :         e->byte_range_end = attribs->byte_range_end;
     151        1768 :         e->low_lat_chunk = attribs->low_latency;
     152        1768 :         e->independent_chunk = attribs->independent_part;
     153             : 
     154        1768 :         e->title = (attribs->title ? gf_strdup(attribs->title) : NULL);
     155        1768 :         e->codecs = (attribs->codecs ? gf_strdup(attribs->codecs) : NULL);
     156        1768 :         e->language = (attribs->language ? gf_strdup(attribs->language) : NULL);
     157        1768 :         e->drm_method = attribs->key_method;
     158        1768 :         e->init_segment_url = attribs->init_url ? gf_strdup(attribs->init_url) : NULL;
     159        1768 :         e->init_byte_range_start = attribs->init_byte_range_start;
     160        1768 :         e->init_byte_range_end = attribs->init_byte_range_end;
     161        1768 :         e->key_uri = (attribs->key_url ? gf_strdup(attribs->key_url) : NULL);
     162        1768 :         memcpy(e->key_iv, attribs->key_iv, sizeof(bin128));
     163             : 
     164        1768 :         e->utc_start_time = attribs->playlist_utc_timestamp;
     165        1768 :         e->discontinuity = attribs->discontinuity;
     166             : 
     167             :         assert(url);
     168        1768 :         e->url = gf_strdup(url);
     169        1768 :         e->bandwidth = 0;
     170        1768 :         e->element_type = element_type;
     171        1768 :         if (element_type == TYPE_PLAYLIST) {
     172         155 :                 e->element.playlist.is_ended = GF_FALSE;
     173         155 :                 e->element.playlist.target_duration = attribs->duration_in_seconds;
     174         155 :                 e->element.playlist.current_media_seq = 0;
     175         155 :                 e->element.playlist.media_seq_min = 0;
     176         155 :                 e->element.playlist.media_seq_max = 0;
     177         155 :                 e->element.playlist.elements = gf_list_new();
     178         155 :                 if (NULL == (e->element.playlist.elements)) {
     179           0 :                         if (e->title)
     180           0 :                                 gf_free(e->title);
     181           0 :                         if (e->codecs)
     182           0 :                                 gf_free(e->codecs);
     183           0 :                         if (e->language)
     184           0 :                                 gf_free(e->language);
     185           0 :                         if (e->audio_group)
     186           0 :                                 gf_free(e->audio_group);
     187           0 :                         if (e->video_group)
     188           0 :                                 gf_free(e->video_group);
     189           0 :                         if (e->url)
     190           0 :                                 gf_free(e->url);
     191           0 :                         if (e->init_segment_url)
     192           0 :                                 gf_free(e->init_segment_url);
     193           0 :                         if (e->key_uri)
     194           0 :                                 gf_free(e->key_uri);
     195           0 :                         e->url = NULL;
     196           0 :                         e->title = NULL;
     197           0 :                         e->codecs = NULL;
     198           0 :                         e->language = NULL;
     199           0 :                         e->audio_group = NULL;
     200           0 :                         e->video_group = NULL;
     201           0 :                         e->key_uri = NULL;
     202             :                         memset(e->key_iv, 0, sizeof(bin128));
     203           0 :                         gf_free(e);
     204           0 :                         return NULL;
     205             :                 }
     206             :         } else {
     207             :                 /* Nothing to do, media is an empty structure */
     208             :         }
     209             :         assert(e->bandwidth == 0);
     210             :         assert(e->url);
     211             :         return e;
     212             : }
     213             : 
     214             : 
     215             : /********** stream **********/
     216             : 
     217             : /**
     218             :  * Creates a new stream properly initialized
     219             :  */
     220         137 : static Stream* stream_new(int stream_id) {
     221         137 :         Stream *program = (Stream *) gf_malloc(sizeof(Stream));
     222         137 :         if (program == NULL) {
     223             :                 return NULL;
     224             :         }
     225         137 :         program->stream_id = stream_id;
     226         137 :         program->variants = gf_list_new();
     227         137 :         if (program->variants == NULL) {
     228           0 :                 gf_free(program);
     229           0 :                 return NULL;
     230             :         }
     231             :         return program;
     232             : }
     233             : 
     234             : /**
     235             :  * Deletes the specified stream
     236             :  */
     237         137 : static GF_Err stream_del(Stream *stream) {
     238             :         GF_Err e = GF_OK;
     239         137 :         if (stream == NULL)
     240             :                 return e;
     241         137 :         if (stream->variants) {
     242           0 :                 while (gf_list_count(stream->variants)) {
     243           0 :                         GF_List *l = gf_list_get(stream->variants, 0);
     244           0 :                         cleanup_list_of_elements(l);
     245           0 :                         gf_list_rem(stream->variants, 0);
     246             :                 }
     247           0 :                 gf_list_del(stream->variants);
     248             :         }
     249         137 :         stream->variants = NULL;
     250         137 :         gf_free(stream);
     251         137 :         return e;
     252             : }
     253             : 
     254             : 
     255             : 
     256         138 : static GFINLINE int string2num(const char *s) {
     257             :         u64 ret=0, i, shift=2;
     258             :         u8 hash[GF_SHA1_DIGEST_SIZE];
     259         138 :         gf_sha1_csum((u8*)s, (u32)strlen(s), hash);
     260             :         assert(shift*GF_SHA1_DIGEST_SIZE < 64);
     261        2898 :         for (i=0; i<GF_SHA1_DIGEST_SIZE; ++i)
     262        2760 :                 ret += (ret << shift) + hash[i];
     263         138 :         return (int)(ret % MEDIA_TYPE_AUDIO);
     264             : }
     265             : 
     266             : 
     267             : #define GROUP_ID_TO_PROGRAM_ID(type, group_id) (\
     268             :         MEDIA_TYPE_##type + \
     269             :         string2num(group_id) \
     270             :         ) \
     271             :  
     272       19966 : static Bool safe_start_equals(const char *attribute, const char *line) {
     273             :         size_t len, atlen;
     274       19966 :         if (line == NULL)
     275             :                 return GF_FALSE;
     276       19966 :         len = strlen(line);
     277       19966 :         atlen = strlen(attribute);
     278       19966 :         if (len < atlen)
     279             :                 return GF_FALSE;
     280       19684 :         return (0 == strncmp(attribute, line, atlen));
     281             : }
     282             : 
     283             : 
     284             : static void reset_attributes(s_accumulated_attributes *attributes)
     285             : {
     286             :         memset(attributes, 0, sizeof(s_accumulated_attributes));
     287             :         attributes->type = MEDIA_TYPE_UNKNOWN;
     288         124 :         attributes->min_media_sequence = 1;
     289         124 :         attributes->version = 1;
     290             :         attributes->compatibility_version = 0;
     291             :         attributes->key_method = DRM_NONE;
     292             : }
     293             : 
     294       16129 : static char** extract_attributes(const char *name, const char *line, const int num_attributes) {
     295             :         int sz, i, curr_attribute, start;
     296             :         char **ret;
     297             :         u8 quote = 0;
     298       16129 :         int len = (u32) strlen(line);
     299       16129 :         start = (u32) strlen(name);
     300       16129 :         if (len <= start)
     301             :                 return NULL;
     302       13560 :         if (!safe_start_equals(name, line))
     303             :                 return NULL;
     304        2016 :         ret = gf_calloc((num_attributes + 1), sizeof(char*));
     305             :         curr_attribute = 0;
     306       31814 :         for (i=start; i<=len; i++) {
     307       30242 :                 if (line[i] == '\0' || (!quote && line[i] == ',')  || (line[i] == quote)) {
     308             :                         u32 spaces = 0;
     309        3168 :                         sz = i - start;
     310        3168 :                         if (quote && (line[i] == quote))
     311         352 :                                 sz++;
     312             :                         
     313        3188 :                         while (line[start+spaces] == ' ')
     314          20 :                                 spaces++;
     315        3168 :                         if ((sz-spaces<=1) && (line[start+spaces]==',')) {
     316             :                                 //start = i+1;
     317             :                         } else {
     318        2994 :                                 if (!strncmp(&line[start+spaces], "\t", sz-spaces) || !strncmp(&line[start+spaces], "\n", sz-spaces)) {
     319             :                                 } else {
     320        2391 :                                         ret[curr_attribute] = gf_calloc( (1+sz-spaces), sizeof(char));
     321             :                                         strncpy(ret[curr_attribute], &(line[start+spaces]), sz-spaces);
     322        2391 :                                         curr_attribute++;
     323             :                                 }
     324             :                         }
     325        3168 :                         start = i+1;
     326             :                         
     327        3168 :                         if (start == len) {
     328             :                                 return ret;
     329             :                         }
     330             :                 }
     331       29798 :                 if ((line[i] == '\'') || (line[i] == '"'))  {
     332         526 :                         if (quote) {
     333             :                                 quote = 0;
     334             :                         } else {
     335         352 :                                 quote = line[i];
     336             :                         }
     337             :                 }
     338             :         }
     339        1572 :         if (curr_attribute == 0) {
     340           0 :                 gf_free(ret);
     341           0 :                 return NULL;
     342             :         }
     343             :         return ret;
     344             : }
     345             : 
     346             : #define M3U8_COMPATIBILITY_VERSION(v) \
     347             :         if (v > attributes->compatibility_version) \
     348             :                 attributes->compatibility_version = v;
     349             : 
     350             : /**
     351             :  * Parses the attributes and accumulate into the attributes structure
     352             :  */
     353        2237 : static char** parse_attributes(const char *line, s_accumulated_attributes *attributes) {
     354             :         int int_value, i;
     355             :         char **ret;
     356             :         char *end_ptr;
     357        2237 :         if (line == NULL)
     358             :                 return NULL;
     359        2237 :         if (!safe_start_equals("#EXT", line))
     360             :                 return NULL;
     361        2237 :         if (safe_start_equals("#EXT-X-ENDLIST", line)) {
     362          33 :                 attributes->is_playlist_ended = GF_TRUE;
     363          33 :                 M3U8_COMPATIBILITY_VERSION(1);
     364             :                 return NULL;
     365             :         }
     366             :         /* reset not accumated attributes */
     367        2204 :         attributes->type = MEDIA_TYPE_UNKNOWN;
     368             : 
     369        2204 :         ret = extract_attributes("#EXT-X-TARGETDURATION:", line, 1);
     370        2204 :         if (ret) {
     371             :                 /* #EXT-X-TARGETDURATION:<seconds> */
     372         103 :                 if (ret[0]) {
     373         103 :                         int_value = (s32) strtol(ret[0], &end_ptr, 10);
     374         103 :                         if (end_ptr != ret[0]) {
     375         103 :                                 attributes->target_duration_in_seconds = int_value;
     376             :                         }
     377             :                 }
     378         103 :                 M3U8_COMPATIBILITY_VERSION(1);
     379             :                 return ret;
     380             :         }
     381        2101 :         ret = extract_attributes("#EXT-X-MEDIA-SEQUENCE:", line, 1);
     382        2101 :         if (ret) {
     383             :                 /* #EXT-X-MEDIA-SEQUENCE:<number> */
     384         103 :                 if (ret[0]) {
     385         103 :                         int_value = (s32)strtol(ret[0], &end_ptr, 10);
     386         103 :                         if (end_ptr != ret[0]) {
     387         103 :                                 attributes->min_media_sequence = int_value;
     388         103 :                                 attributes->current_media_seq = int_value;
     389             :                         }
     390             :                 }
     391         103 :                 M3U8_COMPATIBILITY_VERSION(1);
     392             :                 return ret;
     393             :         }
     394        1998 :         ret = extract_attributes("#EXT-X-VERSION:", line, 1);
     395        1998 :         if (ret) {
     396             :                 /* #EXT-X-VERSION:<number> */
     397         122 :                 if (ret[0]) {
     398         122 :                         int_value = (s32)strtol(ret[0], &end_ptr, 10);
     399         122 :                         if (end_ptr != ret[0]) {
     400         122 :                                 attributes->version = int_value;
     401             :                         }
     402             :                         //although technically it is mandated for v2 or more, don't complain if set for v1
     403         122 :                         M3U8_COMPATIBILITY_VERSION(1);
     404             :                 }
     405             :                 return ret;
     406             :         }
     407        1876 :         ret = extract_attributes("#EXTINF:", line, 2);
     408        1876 :         if (ret) {
     409         941 :                 M3U8_COMPATIBILITY_VERSION(1);
     410             :                 /* #EXTINF:<duration>,<title> */
     411         941 :                 attributes->is_media_segment = GF_TRUE;
     412         941 :                 if (ret[0]) {
     413         941 :                         double double_value = strtod(ret[0], &end_ptr);
     414         941 :                         if (end_ptr != ret[0]) {
     415         941 :                                 attributes->duration_in_seconds = double_value;
     416             :                         }
     417         941 :                         if (strstr(ret[0], ".") || (double_value > (int)double_value)) {
     418         677 :                                 M3U8_COMPATIBILITY_VERSION(3);
     419             :                         }
     420             :                 }
     421         941 :                 if (ret[1]) {
     422           0 :                         if (attributes->title) gf_free(attributes->title);
     423           0 :                         attributes->title = gf_strdup(ret[1]);
     424             :                 }
     425             :                 return ret;
     426             :         }
     427         935 :         ret = extract_attributes("#EXT-X-KEY:", line, 4);
     428         935 :         if (ret) {
     429             :                 /* #EXT-X-KEY:METHOD=<method>[,URI="<URI>"] */
     430             :                 const char *method = "METHOD=";
     431             :                 const size_t method_len = strlen(method);
     432           9 :                 if (safe_start_equals(method, ret[0])) {
     433           9 :                         if (!strncmp(ret[0]+method_len, "NONE", 4)) {
     434           0 :                                 attributes->key_method = DRM_NONE;
     435           9 :                         } else if (!strncmp(ret[0]+method_len, "AES-128", 7)) {
     436           0 :                                 attributes->key_method = DRM_AES_128;
     437           9 :                         } else if (!strncmp(ret[0]+method_len, "SAMPLE-AES", 10)) {
     438           9 :                                 attributes->key_method = DRM_CENC;
     439             :                         } else {
     440           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] EXT-X-KEY method not recognized.\n"));
     441             :                         }
     442           9 :                         if (ret[1] != NULL && safe_start_equals("URI=\"", ret[1])) {
     443           9 :                                 int_value = (u32) strlen(ret[1]);
     444           9 :                                 if (ret[1][int_value-1] == '"') {
     445           9 :                                         if (attributes->key_url) gf_free(attributes->key_url);
     446           9 :                                         attributes->key_url = gf_strdup(&(ret[1][5]));
     447           9 :                                         if (attributes->key_url) {
     448           9 :                                                 u32 klen = (u32) strlen(attributes->key_url);
     449           9 :                                                 attributes->key_url[klen-1] = 0;
     450             :                                         }
     451             :                                 }
     452             :                         }
     453           9 :                         if (ret[2] != NULL && safe_start_equals("IV=", ret[2])) {
     454           0 :                                 char *IV = ret[2] + 3;
     455           0 :                                 if (!strncmp(IV, "0x", 2)) IV+=2;
     456           0 :                                 if (strlen(IV) != 32) {
     457           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] EXT-X-KEY wrong IV len\n"));
     458             :                                 } else {
     459           0 :                                         for (i=0; i<16; i++) {
     460             :                                                 char szV[3];
     461             :                                                 u32 v;
     462           0 :                                                 szV[0] = IV[2*i];
     463           0 :                                                 szV[1] = IV[2*i + 1];
     464           0 :                                                 szV[2] = 0;
     465           0 :                                                 sscanf(szV, "%X", &v);
     466           0 :                                                 attributes->key_iv[i] = v;
     467             :                                         }
     468             :                                 }
     469             :                         } else {
     470           9 :                                 u32 iv = gf_htonl(attributes->current_media_seq);
     471           9 :                                 memset(attributes->key_iv, 0, sizeof(bin128) );
     472           9 :                                 memcpy(attributes->key_iv + 12, (const void *) &iv, sizeof(iv));
     473             :                         }
     474             :                 }
     475           9 :                 M3U8_COMPATIBILITY_VERSION(1);
     476             :                 return ret;
     477             :         }
     478         926 :         ret = extract_attributes("#EXT-X-PROGRAM-DATE-TIME:", line, 1);
     479         926 :         if (ret) {
     480             :                 /* #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ> */
     481           0 :                 if (ret[0]) attributes->playlist_utc_timestamp = gf_net_parse_date(ret[0]);
     482           0 :                 M3U8_COMPATIBILITY_VERSION(1);
     483             :                 return ret;
     484             :         }
     485         926 :         ret = extract_attributes("#EXT-X-ALLOW-CACHE:", line, 1);
     486         926 :         if (ret) {
     487             :                 /* #EXT-X-ALLOW-CACHE:<YES|NO> */
     488           0 :                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH,("[M3U8] EXT-X-ALLOW-CACHE not supported.\n", line));
     489           0 :                 M3U8_COMPATIBILITY_VERSION(1);
     490             :                 return ret;
     491             :         }
     492         926 :         ret = extract_attributes("#EXT-X-PLAYLIST-TYPE", line, 1);
     493         926 :         if (ret) {
     494           6 :                 if (ret[0] && !strcmp(ret[0], "VOD")) attributes->is_playlist_ended = GF_TRUE;
     495           6 :                 M3U8_COMPATIBILITY_VERSION(3);
     496             :                 return ret;
     497             :         }
     498         920 :         ret = extract_attributes("#EXT-X-MAP", line, 4);
     499         920 :         if (ret) {
     500             :                 /* #EXT-X-MAP:URI="<URI>"] */
     501             :                 i=0;
     502         207 :                 while (ret[i] != NULL) {
     503             :                         char *val = ret[i];
     504         107 :                         if (val[0]==':') val++;
     505         107 :                         if (safe_start_equals("URI=\"", val)) {
     506         100 :                                 char *uri = val + 5;
     507         100 :                                 int_value = (u32) strlen(uri);
     508         100 :                                 if (uri[int_value-1] == '"') {
     509         100 :                                         if (attributes->init_url) gf_free(attributes->init_url);
     510         100 :                                         attributes->init_url = gf_strdup(uri);
     511         100 :                                         attributes->init_url[int_value-1]=0;
     512             :                                 }
     513             :                         }
     514           7 :                         else if (safe_start_equals("BYTERANGE=\"", val)) {
     515             :                                 u64 begin, size;
     516           7 :                                 val+=10;
     517           7 :                                 if (sscanf(val, "\""LLU"@"LLU"\"", &size, &begin) == 2) {
     518           7 :                                         if (size) {
     519           7 :                                                 attributes->init_byte_range_start = begin;
     520           7 :                                                 attributes->init_byte_range_end = begin + size - 1;
     521             :                                         } else {
     522           0 :                                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Invalid byte range %s\n", val));
     523             :                                         }
     524             :                                 }
     525             :                         }
     526         107 :                         i++;
     527             :                 }
     528         100 :                 M3U8_COMPATIBILITY_VERSION(3);
     529             :                 return ret;
     530             :         }
     531         820 :         ret = extract_attributes("#EXT-X-STREAM-INF:", line, 10);
     532         820 :         if (ret) {
     533             :                 /* #EXT-X-STREAM-INF:[attribute=value][,attribute=value]* */
     534             :                 i = 0;
     535          55 :                 attributes->is_master_playlist = GF_TRUE;
     536          55 :                 M3U8_COMPATIBILITY_VERSION(1);
     537         381 :                 while (ret[i] != NULL) {
     538             :                         char *utility;
     539         326 :                         if (safe_start_equals("BANDWIDTH=", ret[i])) {
     540          55 :                                 utility = &(ret[i][10]);
     541          55 :                                 int_value = (s32) strtol(utility, &end_ptr, 10);
     542          55 :                                 if (end_ptr != utility)
     543          55 :                                         attributes->bandwidth = int_value;
     544         271 :                         } else if (safe_start_equals("PROGRAM-ID=", ret[i])) {
     545           0 :                                 utility = &(ret[i][11]);
     546           0 :                                 int_value = (s32) strtol(utility, &end_ptr, 10);
     547           0 :                                 if (end_ptr != utility)
     548           0 :                                         attributes->stream_id = int_value;
     549         271 :                         } else if (safe_start_equals("CODECS=\"", ret[i])) {
     550          55 :                                 int_value = (u32) strlen(ret[i]);
     551          55 :                                 if (ret[i][int_value-1] == '"') {
     552          55 :                                         if (attributes->codecs) gf_free(attributes->codecs);
     553          55 :                                         attributes->codecs = gf_strdup(&(ret[i][7]));
     554             :                                 }
     555         216 :                         } else if (safe_start_equals("RESOLUTION=", ret[i])) {
     556             :                                 u32 w, h;
     557          55 :                                 utility = &(ret[i][11]);
     558          55 :                                 if ((sscanf(utility, "%dx%d", &w, &h)==2) || (sscanf(utility, "%dx%d,", &w, &h)==2)) {
     559          55 :                                         attributes->width = w;
     560          55 :                                         attributes->height = h;
     561             :                                 }
     562          55 :                                 M3U8_COMPATIBILITY_VERSION(2);
     563         161 :                         } else if (safe_start_equals("AUDIO=", ret[i])) {
     564             :                                 assert(attributes->type == MEDIA_TYPE_UNKNOWN);
     565          40 :                                 attributes->type = MEDIA_TYPE_AUDIO;
     566          40 :                                 if (attributes->group.audio) gf_free(attributes->group.audio);
     567          40 :                                 attributes->group.audio = gf_strdup(ret[i] + 6);
     568          40 :                                 M3U8_COMPATIBILITY_VERSION(4);
     569         121 :                         } else if (safe_start_equals("VIDEO=", ret[i])) {
     570             :                                 assert(attributes->type == MEDIA_TYPE_UNKNOWN);
     571           0 :                                 attributes->type = MEDIA_TYPE_VIDEO;
     572           0 :                                 if (attributes->group.video) gf_free(attributes->group.video);
     573           0 :                                 attributes->group.video = gf_strdup(ret[i] + 6);
     574           0 :                                 M3U8_COMPATIBILITY_VERSION(4);
     575             :                         }
     576         326 :                         i++;
     577             :                 }
     578          55 :                 if (!attributes->bandwidth) {
     579           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-STREAM-INF: no BANDWIDTH found. Ignoring the line.\n"));
     580             :                         return NULL;
     581             :                 }
     582             :                 return ret;
     583             :         }
     584         765 :         ret = extract_attributes("#EXT-X-DISCONTINUITY", line, 0);
     585         765 :         if (ret) {
     586           0 :                 attributes->discontinuity = 1;
     587           0 :                 M3U8_COMPATIBILITY_VERSION(1);
     588             :                 return ret;
     589             :         }
     590         765 :         ret = extract_attributes("#EXT-X-DISCONTINUITY-SEQUENCE", line, 0);
     591         765 :         if (ret) {
     592           0 :                 if (ret[0]) {
     593           0 :                         int_value = (s32)strtol(ret[0], &end_ptr, 10);
     594           0 :                         if (end_ptr != ret[0]) {
     595           0 :                                 attributes->discontinuity = int_value;
     596             :                         }
     597             :                 }
     598           0 :                 M3U8_COMPATIBILITY_VERSION(1);
     599             :                 return ret;
     600             :         }
     601         765 :         ret = extract_attributes("#EXT-X-BYTERANGE:", line, 1);
     602         765 :         if (ret) {
     603             :                 /* #EXT-X-BYTERANGE:<begin@end> */
     604         563 :                 if (ret[0]) {
     605             :                         u64 begin, size;
     606         563 :                         if (sscanf(ret[0], LLU"@"LLU, &size, &begin) == 2) {
     607         563 :                                 if (size) {
     608         563 :                                         attributes->byte_range_start = begin;
     609         563 :                                         attributes->byte_range_end = begin + size - 1;
     610             :                                 } else {
     611           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Invalid byte range %s\n", ret[0]));
     612             :                                 }
     613             :                         }
     614             :                 }
     615         563 :                 M3U8_COMPATIBILITY_VERSION(4);
     616             :                 return ret;
     617             :         }
     618         202 :         ret = extract_attributes("#EXT-X-MEDIA:", line, 14);
     619         202 :         if (ret) {
     620             :                 /* #EXT-X-MEDIA:[TYPE={AUDIO,VIDEO}],[URI],[GROUP-ID],[LANGUAGE],[NAME],[DEFAULT={YES,NO}],[AUTOSELECT={YES,NO}] */
     621          14 :                 M3U8_COMPATIBILITY_VERSION(4);
     622          14 :                 attributes->is_master_playlist = GF_TRUE;
     623             :                 i = 0;
     624         130 :                 while (ret[i] != NULL) {
     625         102 :                         if (safe_start_equals("TYPE=", ret[i])) {
     626          14 :                                 if (!strncmp(ret[i]+5, "AUDIO", 5)) {
     627          12 :                                         attributes->type = MEDIA_TYPE_AUDIO;
     628           2 :                                 } else if (!strncmp(ret[i]+5, "VIDEO", 5)) {
     629           0 :                                         attributes->type = MEDIA_TYPE_VIDEO;
     630           2 :                                 } else if (!strncmp(ret[i]+5, "SUBTITLES", 9)) {
     631           1 :                                         attributes->type = MEDIA_TYPE_SUBTITLES;
     632           1 :                                 } else if (!strncmp(ret[i]+5, "CLOSED-CAPTIONS", 15)) {
     633           1 :                                         attributes->type = MEDIA_TYPE_CLOSED_CAPTIONS;
     634             :                                 } else {
     635           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Unsupported #EXT-X-MEDIA:TYPE=%s\n", ret[i]+5));
     636             :                                 }
     637          88 :                         } else if (safe_start_equals("URI=\"", ret[i])) {
     638             :                                 size_t len;
     639          13 :                                 if (attributes->mediaURL) gf_free(attributes->mediaURL);
     640          13 :                                 attributes->mediaURL = gf_strdup(ret[i]+5);
     641          13 :                                 len = strlen(attributes->mediaURL);
     642          13 :                                 if (len && (attributes->mediaURL[len-1] == '"')) {
     643          13 :                                         attributes->mediaURL[len-1] = '\0';
     644             :                                 } else {
     645           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Misformed #EXT-X-MEDIA:URI=%s. Quotes are incorrect.\n", ret[i]+5));
     646             :                                 }
     647          75 :                         } else if (safe_start_equals("GROUP-ID=", ret[i])) {
     648          14 :                                 if (attributes->type == MEDIA_TYPE_AUDIO) {
     649          12 :                                         if (attributes->group.audio) gf_free(attributes->group.audio);
     650          12 :                                         attributes->group.audio = gf_strdup(ret[i]+9);
     651          12 :                                         attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(AUDIO, attributes->group.audio);
     652           2 :                                 } else if (attributes->type == MEDIA_TYPE_VIDEO) {
     653           0 :                                         if (attributes->group.video) gf_free(attributes->group.video);
     654           0 :                                         attributes->group.video = gf_strdup(ret[i]+9);
     655           0 :                                         attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(VIDEO, attributes->group.video);
     656           2 :                                 } else if (attributes->type == MEDIA_TYPE_SUBTITLES) {
     657           1 :                                         if (attributes->group.subtitle) gf_free(attributes->group.subtitle);
     658           1 :                                         attributes->group.subtitle = gf_strdup(ret[i]+9);
     659           1 :                                         attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(SUBTITLES, attributes->group.subtitle);
     660           1 :                                 } else if (attributes->type == MEDIA_TYPE_CLOSED_CAPTIONS) {
     661           1 :                                         if (attributes->group.closed_captions) gf_free(attributes->group.closed_captions);
     662           1 :                                         attributes->group.closed_captions = gf_strdup(ret[i]+9);
     663           1 :                                         attributes->stream_id = GROUP_ID_TO_PROGRAM_ID(CLOSED_CAPTIONS, attributes->group.closed_captions);
     664           0 :                                 } else if (attributes->type == MEDIA_TYPE_UNKNOWN) {
     665           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA:GROUP-ID=%s. Ignoring the line.\n", ret[i]+9));
     666             :                                         return NULL;
     667             :                                 }
     668          61 :                         } else if (safe_start_equals("LANGUAGE=\"", ret[i])) {
     669             :                                 size_t len;
     670          14 :                                 if (attributes->language) gf_free(attributes->language);
     671          14 :                                 attributes->language = gf_strdup(ret[i]+9);
     672          14 :                                 len = strlen(attributes->language);
     673          14 :                                 if (len && (attributes->language[len-1] == '"')) {
     674          14 :                                         attributes->language[len-1] = '\0';
     675             :                                 } else {
     676           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Misformed #EXT-X-MEDIA:LANGUAGE=%s. Quotes are incorrect.\n", ret[i]+5));
     677             :                                 }
     678          47 :                         } else if (safe_start_equals("NAME=", ret[i])) {
     679          14 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH,("[M3U8] EXT-X-MEDIA:NAME not supported\n"));
     680             :                                 //attributes->name = gf_strdup(ret[i]+5);
     681          33 :                         } else if (safe_start_equals("DEFAULT=", ret[i])) {
     682           5 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH,("[M3U8] EXT-X-MEDIA:DEFAULT not supported\n"));
     683           5 :                                 if (!strncmp(ret[i]+8, "YES", 3)) {
     684             :                                         //TODO
     685           0 :                                 } else if (!strncmp(ret[i]+8, "NO", 2)) {
     686             :                                         //TODO
     687             :                                 } else {
     688           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA:DEFAULT=%s\n", ret[i]+8));
     689             :                                 }
     690          28 :                         } else if (safe_start_equals("AUTOSELECT=", ret[i])) {
     691          14 :                                 GF_LOG(GF_LOG_INFO, GF_LOG_DASH,("[M3U8] EXT-X-MEDIA:AUTOSELECT not supported\n"));
     692          14 :                                 if (!strncmp(ret[i]+11, "YES", 3)) {
     693             :                                         //TODO
     694           0 :                                 } else if (!strncmp(ret[i]+11, "NO", 2)) {
     695             :                                         //TODO
     696             :                                 } else {
     697           0 :                                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA:AUTOSELECT=%s\n", ret[i]+11));
     698             :                                 }
     699             :                         }
     700             : 
     701         102 :                         i++;
     702             :                 }
     703             : 
     704          14 :                 if (attributes->type == MEDIA_TYPE_UNKNOWN) {
     705           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA: TYPE is missing. Ignoring the line.\n"));
     706             :                         return NULL;
     707             :                 }
     708          14 :                 if (attributes->type == MEDIA_TYPE_CLOSED_CAPTIONS && attributes->mediaURL) {
     709           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA: TYPE is CLOSED-CAPTIONS but URI is present. Ignoring the URI.\n"));
     710           0 :                         gf_free(attributes->mediaURL);
     711           0 :                         attributes->mediaURL = NULL;
     712             :                 }
     713          14 :                 if ((attributes->type == MEDIA_TYPE_AUDIO && !attributes->group.audio)
     714          14 :                         || (attributes->type == MEDIA_TYPE_VIDEO && !attributes->group.video)) {
     715           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA: missing GROUP-ID attribute. Ignoring the line.\n"));
     716             :                         return NULL;
     717             :                 }
     718          14 :                 if (!attributes->stream_id) {
     719           0 :                         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Invalid #EXT-X-MEDIA: no ID was computed. Check previous errors. Ignoring the line.\n"));
     720             :                         return NULL;
     721             :                 }
     722             : 
     723             :                 return ret;
     724             :         }
     725         188 :         if (!strncmp(line, "#EXT-X-INDEPENDENT-SEGMENTS", strlen("#EXT-X-INDEPENDENT-SEGMENTS") )) {
     726         121 :                 attributes->independent_segments = GF_TRUE;
     727         121 :                 M3U8_COMPATIBILITY_VERSION(1);
     728             :                 return NULL;
     729             :         }
     730          67 :         if (!strncmp(line, "#EXT-X-I-FRAME-STREAM-INF", strlen("#EXT-X-I-FRAME-STREAM-INF") )) {
     731             :                 //todo extract I/intra rate for speed adaptation
     732             :                 return NULL;
     733             :         }
     734          61 :         if (!strncmp(line, "#EXT-X-PART-INF", strlen("#EXT-X-PART-INF") )) {
     735          61 :                 attributes->low_latency = GF_TRUE;
     736          61 :                 return NULL;
     737             :         }
     738             :         //TODO for now we don't use preload hint
     739           0 :         if (!strncmp(line, "#EXT-X-SERVER-CONTROL", strlen("#EXT-X-SERVER-CONTROL") )) {
     740             :                 return NULL;
     741             :         }
     742             :         //TODO for now we don't use preload hint
     743           0 :         if (!strncmp(line, "#EXT-X-PRELOAD-HINT", strlen("#EXT-X-PRELOAD-HINT") )) {
     744             :                 return NULL;
     745             :         }
     746           0 :         GF_LOG(GF_LOG_WARNING, GF_LOG_DASH,("[M3U8] Unsupported directive %s\n", line));
     747             :         return NULL;
     748             : }
     749             : 
     750             : /**
     751             :  * Creates a new MasterPlaylist
     752             : \param NULL if MasterPlaylist element could not be allocated
     753             :  */
     754         124 : MasterPlaylist* master_playlist_new()
     755             : {
     756             :         MasterPlaylist *pl;
     757         124 :         GF_SAFEALLOC(pl, MasterPlaylist);
     758             : 
     759         124 :         if (pl == NULL)
     760             :                 return NULL;
     761         124 :         pl->streams = gf_list_new();
     762         124 :         if (!pl->streams) {
     763           0 :                 gf_free(pl);
     764           0 :                 return NULL;
     765             :         }
     766         124 :         pl->current_stream = -1;
     767         124 :         pl->playlist_needs_refresh = GF_TRUE;
     768         124 :         return pl;
     769             : }
     770             : 
     771             : 
     772             : /********** master_playlist **********/
     773             : 
     774         124 : GF_Err gf_m3u8_master_playlist_del(MasterPlaylist **playlist) {
     775         124 :         if ((playlist == NULL) || (*playlist == NULL))
     776             :                 return GF_OK;
     777             :         assert((*playlist)->streams);
     778         261 :         while (gf_list_count((*playlist)->streams)) {
     779         137 :                 Stream *p = gf_list_get((*playlist)->streams, 0);
     780             :                 assert(p);
     781         429 :                 while (gf_list_count(p->variants)) {
     782         155 :                         PlaylistElement *pl = gf_list_get(p->variants, 0);
     783             :                         assert(pl);
     784         155 :                         playlist_element_del(pl);
     785         155 :                         gf_list_rem(p->variants, 0);
     786             :                 }
     787         137 :                 gf_list_del(p->variants);
     788         137 :                 p->variants = NULL;
     789         137 :                 stream_del(p);
     790         137 :                 gf_list_rem((*playlist)->streams, 0);
     791             :         }
     792         124 :         gf_list_del((*playlist)->streams);
     793         124 :         (*playlist)->streams = NULL;
     794         124 :         gf_free(*playlist);
     795         124 :         *playlist = NULL;
     796             : 
     797         124 :         return GF_OK;
     798             : }
     799             : 
     800        1681 : static Stream* master_playlist_find_matching_stream(const MasterPlaylist *pl, const u32 stream_id) {
     801             :         u32 count, i;
     802             :         assert(pl);
     803             :         assert(pl->streams);
     804             :         assert(stream_id >= 0);
     805        1681 :         count = gf_list_count(pl->streams);
     806          19 :         for (i=0; i<count; i++) {
     807        1563 :                 Stream *cur = gf_list_get(pl->streams, i);
     808             :                 assert(cur);
     809        1563 :                 if (stream_id == cur->stream_id) {
     810             :                         /* We found the program */
     811             :                         return cur;
     812             :                 }
     813             :         }
     814             :         return NULL;
     815             : }
     816             : 
     817             : 
     818             : /********** sub_playlist **********/
     819             : 
     820             : #define M3U8_BUF_SIZE 2048
     821             : 
     822             : GF_EXPORT
     823         124 : GF_Err gf_m3u8_parse_master_playlist(const char *file, MasterPlaylist **playlist, const char *baseURL)
     824             : {
     825             : #ifdef GPAC_ENABLE_COVERAGE
     826         124 :         if (gf_sys_is_cov_mode()) {
     827         124 :                 string2num("coverage");
     828             :         }
     829             : #endif
     830         124 :         return gf_m3u8_parse_sub_playlist(file, playlist, baseURL, NULL, NULL);
     831             : }
     832             : 
     833        1681 : GF_Err declare_sub_playlist(char *currentLine, const char *baseURL, s_accumulated_attributes *attribs, PlaylistElement *sub_playlist, MasterPlaylist **playlist, Stream *in_stream)
     834             : {
     835             :         u32 i, count;
     836             : 
     837             :         char *fullURL = currentLine;
     838             : 
     839        1681 :         if (attribs->is_master_playlist && attribs->is_media_segment) {
     840           0 :                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] Media segment tag MUST NOT appear in a Master Playlist\n"));
     841             :                 return GF_BAD_PARAM;
     842             :         }
     843             : 
     844        1681 :         if (gf_url_is_local(fullURL)) {
     845        1681 :                 fullURL = gf_url_concatenate(baseURL, fullURL);
     846             :                 assert(fullURL);
     847             :         }
     848             : 
     849        1681 :         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] declaring %s %s\n", attribs->is_master_playlist ? "sub-playlist" : "media segment", fullURL));
     850             : 
     851             :         {
     852             :                 PlaylistElement *curr_playlist = sub_playlist;
     853             :                 /* First, we have to find the matching stream */
     854             :                 Stream *stream = in_stream;
     855        1681 :                 if (!in_stream)
     856        1681 :                         stream = master_playlist_find_matching_stream(*playlist, attribs->stream_id);
     857             :                 /* We did not found the stream, we create it */
     858        1681 :                 if (stream == NULL) {
     859         137 :                         stream = stream_new(attribs->stream_id);
     860         137 :                         if (stream == NULL) {
     861             :                                 /* out of memory */
     862           0 :                                 gf_m3u8_master_playlist_del(playlist);
     863           0 :                                 return GF_OUT_OF_MEM;
     864             :                         }
     865         137 :                         gf_list_add((*playlist)->streams, stream);
     866             :                         /* take the first regular variant stream */
     867         137 :                         if ((*playlist)->current_stream < 0 && stream->stream_id < MEDIA_TYPE_AUDIO)
     868         124 :                                 (*playlist)->current_stream = stream->stream_id;
     869             :                 }
     870             : 
     871             :                 /* OK, we have a stream, we have to choose the elements within the same stream variant */
     872             :                 assert(stream);
     873             :                 assert(stream->variants);
     874        1681 :                 count = gf_list_count(stream->variants);
     875             : 
     876        1681 :                 if (!curr_playlist) {
     877          95 :                         for (i=0; i<(s32)count; i++) {
     878        1621 :                                 PlaylistElement *i_playlist_element = gf_list_get(stream->variants, i);
     879             :                                 assert(i_playlist_element);
     880        1621 :                                 if (stream->stream_id < MEDIA_TYPE_AUDIO) {
     881             :                                         /* regular stream (EXT-X-STREAM-INF) */
     882             :                                         // Two stream are identical only if they have the same URL
     883        1621 :                                         if (attribs->is_media_segment || !strcmp(i_playlist_element->url, fullURL)) {
     884             :                                                 curr_playlist = i_playlist_element;
     885             :                                                 break;
     886             :                                         }
     887             :                                 } else {
     888             :                                         /* group streams (EXT-X-MEDIA) */
     889             :                                         //TODO: add renditions and compare depending on context parameters
     890             :                                 }
     891             :                         }
     892             :                 }
     893             : 
     894             :                 /* We are the Master Playlist */
     895        1681 :                 if (attribs->is_master_playlist) {
     896          68 :                         if (curr_playlist != NULL) {
     897             :                                 //playlist has already been defined - this happens when the same video playlist is defined several times with different audio codecs ...
     898          16 :                                 gf_free(fullURL);
     899          16 :                                 return GF_OK;
     900             :                         }
     901          52 :                         curr_playlist = playlist_element_new(TYPE_PLAYLIST, fullURL, attribs);
     902          52 :                         if (curr_playlist == NULL) {
     903             :                                 /* out of memory */
     904           0 :                                 gf_m3u8_master_playlist_del(playlist);
     905           0 :                                 return GF_OUT_OF_MEM;
     906             :                         }
     907             :                         assert(fullURL);
     908          52 :                         if (curr_playlist->url)
     909          52 :                                 gf_free(curr_playlist->url);
     910          52 :                         curr_playlist->url = gf_strdup(fullURL);
     911          52 :                         if (curr_playlist->title)
     912           0 :                                 gf_free(curr_playlist->title);
     913          52 :                         curr_playlist->title = attribs->title ? gf_strdup(attribs->title) : NULL;
     914          52 :                         if (curr_playlist->codecs)
     915          39 :                                 gf_free(curr_playlist->codecs);
     916          52 :                         curr_playlist->codecs = attribs->codecs ? gf_strdup(attribs->codecs) : NULL;
     917          52 :                         if (curr_playlist->audio_group) {
     918           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] Warning: found an AUDIO group in the master playlist."));
     919             :                         }
     920          52 :                         if (curr_playlist->video_group) {
     921           0 :                                 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] Warning: found an VIDEO group in the master playlist."));
     922             :                         }
     923          52 :                         gf_list_add(stream->variants, curr_playlist);
     924          52 :                         curr_playlist->width = attribs->width;
     925          52 :                         curr_playlist->height = attribs->height;
     926             :                 } else {
     927             :                         /* Normal Playlist */
     928             :                         assert((*playlist)->streams);
     929        1613 :                         if (curr_playlist == NULL) {
     930             : 
     931             :                                 /* This is a "normal" playlist without any element in it */
     932             :                                 PlaylistElement *subElement;
     933             :                                 assert(baseURL);
     934         103 :                                 curr_playlist = playlist_element_new(TYPE_PLAYLIST, baseURL, attribs);
     935         103 :                                 if (curr_playlist == NULL) {
     936             :                                         /* out of memory */
     937           0 :                                         gf_m3u8_master_playlist_del(playlist);
     938           0 :                                         return GF_OUT_OF_MEM;
     939             :                                 }
     940             :                                 assert(curr_playlist->element.playlist.elements);
     941             :                                 assert(fullURL);
     942             :                                 assert(curr_playlist->url && !curr_playlist->codecs);
     943         103 :                                 curr_playlist->codecs = NULL;
     944         103 :                                 subElement = playlist_element_new(TYPE_UNKNOWN, fullURL, attribs);
     945         103 :                                 if (subElement == NULL) {
     946           0 :                                         gf_m3u8_master_playlist_del(playlist);
     947           0 :                                         playlist_element_del(curr_playlist);
     948           0 :                                         return GF_OUT_OF_MEM;
     949             :                                 }
     950         103 :                                 gf_list_add(curr_playlist->element.playlist.elements, subElement);
     951         103 :                                 gf_list_add(stream->variants, curr_playlist);
     952         103 :                                 curr_playlist->element.playlist.computed_duration += subElement->duration_info;
     953             :                                 assert(stream);
     954             :                                 assert(stream->variants);
     955             :                                 assert(curr_playlist);
     956             :                         } else {
     957        1510 :                                 PlaylistElement *subElement = playlist_element_new(TYPE_UNKNOWN, fullURL, attribs);
     958        1510 :                                 if (curr_playlist->element_type != TYPE_PLAYLIST) {
     959           0 :                                         curr_playlist->element_type = TYPE_PLAYLIST;
     960           0 :                                         if (!curr_playlist->element.playlist.elements)
     961           0 :                                                 curr_playlist->element.playlist.elements = gf_list_new();
     962             :                                 }
     963        1510 :                                 if (subElement == NULL) {
     964           0 :                                         gf_m3u8_master_playlist_del(playlist);
     965           0 :                                         playlist_element_del(curr_playlist);
     966           0 :                                         return GF_OUT_OF_MEM;
     967             :                                 }
     968        1510 :                                 gf_list_add(curr_playlist->element.playlist.elements, subElement);
     969        1510 :                                 curr_playlist->element.playlist.computed_duration += subElement->duration_info;
     970             :                         }
     971             :                 }
     972             : 
     973        1665 :                 curr_playlist->element.playlist.current_media_seq = attribs->current_media_seq;
     974             :                 /* We first set the default duration for element, aka targetDuration */
     975        1665 :                 if (attribs->target_duration_in_seconds > 0) {
     976        1613 :                         curr_playlist->element.playlist.target_duration = attribs->target_duration_in_seconds;
     977        1613 :                         curr_playlist->duration_info = attribs->target_duration_in_seconds;
     978             :                 }
     979        1665 :                 if (attribs->duration_in_seconds) {
     980        1567 :                         if (curr_playlist->duration_info == 0) {
     981             :                                 /* we set the playlist duration info as the duration of a segment, only if it's not set
     982             :                                 There are cases of playlist with the last segment with a duration different from the others
     983             :                                 (example: Apple bipbop test)*/
     984           0 :                                 curr_playlist->duration_info = attribs->duration_in_seconds;
     985             :                         }
     986             :                 }
     987        1665 :                 curr_playlist->element.playlist.media_seq_min = attribs->min_media_sequence;
     988        1665 :                 curr_playlist->element.playlist.media_seq_max = attribs->current_media_seq;
     989        1665 :                 curr_playlist->element.playlist.discontinuity = attribs->discontinuity;
     990        1665 :                 if (attribs->bandwidth > 1)
     991          40 :                         curr_playlist->bandwidth = attribs->bandwidth;
     992        1665 :                 if (attribs->is_playlist_ended)
     993           0 :                         curr_playlist->element.playlist.is_ended = GF_TRUE;
     994             :         }
     995             :         /* Cleanup all line-specific fields */
     996        1665 :         if (attribs->title) {
     997           0 :                 gf_free(attribs->title);
     998           0 :                 attribs->title = NULL;
     999             :         }
    1000        1665 :         attribs->duration_in_seconds = 0;
    1001        1665 :         attribs->playlist_utc_timestamp = 0;
    1002        1665 :         attribs->bandwidth = 0;
    1003        1665 :         attribs->stream_id = 0;
    1004        1665 :         if (attribs->codecs != NULL) {
    1005          39 :                 gf_free(attribs->codecs);
    1006          39 :                 attribs->codecs = NULL;
    1007             :         }
    1008        1665 :         if (attribs->language != NULL) {
    1009          13 :                 gf_free(attribs->language);
    1010          13 :                 attribs->language = NULL;
    1011             :         }
    1012        1665 :         if (attribs->group.audio != NULL) {
    1013          37 :                 gf_free(attribs->group.audio);
    1014          37 :                 attribs->group.audio = NULL;
    1015             :         }
    1016        1665 :         if (attribs->group.video != NULL) {
    1017           0 :                 gf_free(attribs->group.video);
    1018           0 :                 attribs->group.video = NULL;
    1019             :         }
    1020        1665 :         if (fullURL != currentLine) {
    1021        1665 :                 gf_free(fullURL);
    1022             :         }
    1023             :         return GF_OK;
    1024             : }
    1025             : 
    1026             : typedef struct
    1027             : {
    1028             :         char *name;
    1029             :         u64 start;
    1030             :         u32 size;
    1031             :         Double duration;
    1032             : } HLS_LLChunk;
    1033             : 
    1034        1120 : static void reset_attribs(s_accumulated_attributes *attribs)
    1035             : {
    1036        1120 :         attribs->width = attribs->height = 0;
    1037             : #define RST_ATTR(_name) if (attribs->_name) { gf_free(attribs->_name); attribs->_name = NULL; }
    1038             : 
    1039        1120 :         RST_ATTR(codecs)
    1040        1120 :         RST_ATTR(group.audio)
    1041        1120 :         RST_ATTR(language)
    1042        1120 :         RST_ATTR(title)
    1043        1120 :         RST_ATTR(key_url)
    1044        1120 :         RST_ATTR(init_url)
    1045        1120 :         RST_ATTR(mediaURL)
    1046        1120 : }
    1047             : 
    1048             : 
    1049         124 : GF_Err gf_m3u8_parse_sub_playlist(const char *m3u8_file, MasterPlaylist **playlist, const char *baseURL, Stream *in_stream, PlaylistElement *sub_playlist)
    1050             : {
    1051             :         int i, currentLineNumber;
    1052             :         FILE *f = NULL;
    1053             :         u8 *m3u8_payload;
    1054             :         u32 m3u8_size, m3u8pos;
    1055             :         char currentLine[M3U8_BUF_SIZE];
    1056             :         char **attributes = NULL;
    1057             :         Bool release_blob = GF_FALSE;
    1058             :         s_accumulated_attributes attribs;
    1059             : 
    1060         124 :         if (!strncmp(m3u8_file, "gmem://", 7)) {
    1061          72 :                 GF_Err e = gf_blob_get(m3u8_file, &m3u8_payload,  &m3u8_size, NULL);
    1062          72 :                 if (e) {
    1063           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot Open m3u8 source %s for reading\n", m3u8_file));
    1064             :                         return e;
    1065             :                 }
    1066             :                 release_blob = GF_TRUE;
    1067             :         } else {
    1068          52 :                 f = gf_fopen(m3u8_file, "rt");
    1069          52 :                 if (!f) {
    1070           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot open m3u8 file %s for reading\n", m3u8_file));
    1071             :                         return GF_URL_ERROR;
    1072             :                 }
    1073             :         }
    1074             : 
    1075             :         memset(&attribs, 0, sizeof(s_accumulated_attributes));
    1076             : 
    1077             : #define _CLEANUP \
    1078             :         reset_attribs(&attribs);\
    1079             :         if (f) gf_fclose(f); \
    1080             :         else if (release_blob) gf_blob_release(m3u8_file);
    1081             : 
    1082             : 
    1083         124 :         if (*playlist == NULL) {
    1084         124 :                 *playlist = master_playlist_new();
    1085         124 :                 if (!(*playlist)) {
    1086           0 :                         _CLEANUP
    1087             :                         return GF_OUT_OF_MEM;
    1088             :                 }
    1089             :         }
    1090             :         currentLineNumber = 0;
    1091             :         reset_attributes(&attribs);
    1092             :         m3u8pos = 0;
    1093             :         while (1) {
    1094             :                 char *eof;
    1095             :                 int len;
    1096        4247 :                 if (f) {
    1097        1063 :                         if (!gf_fgets(currentLine, sizeof(currentLine), f))
    1098             :                                 break;
    1099             :                 } else {
    1100             :                         u32 __idx = 0;
    1101        3184 :                         if (m3u8pos >= m3u8_size)
    1102             :                                 break;
    1103             :                         while (1) {
    1104             :                                 assert(__idx < M3U8_BUF_SIZE);
    1105             : 
    1106      117929 :                                 currentLine[__idx] = m3u8_payload[m3u8pos];
    1107      117929 :                                 __idx++;
    1108      117929 :                                 m3u8pos++;
    1109      117929 :                                 if ((currentLine[__idx-1]=='\n') || (currentLine[__idx-1]=='\r') || (m3u8pos >= m3u8_size)) {
    1110        3112 :                                         currentLine[__idx]=0;
    1111        3112 :                                         break;
    1112             :                                 }
    1113             :                         }
    1114             :                 }
    1115        4123 :                 currentLineNumber++;
    1116        4123 :                 eof = strchr(currentLine, '\r');
    1117        4123 :                 if (eof)
    1118           0 :                         eof[0] = '\0';
    1119        4123 :                 eof = strchr(currentLine, '\n');
    1120        4123 :                 if (eof)
    1121        4117 :                         eof[0] = '\0';
    1122        4123 :                 len = (u32) strlen(currentLine);
    1123        4123 :                 if (len < 1)
    1124          94 :                         continue;
    1125        4029 :                 if (currentLineNumber == 1) {
    1126             :                         /* Playlist MUST start with #EXTM3U */
    1127         124 :                         if (len < 7 || (strncmp("#EXTM3U", currentLine, 7) != 0)) {
    1128           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Failed to parse M3U8 File, it should start with #EXTM3U, but was : %s\n", currentLine));
    1129           0 :                                 _CLEANUP
    1130             :                                 return GF_STREAM_NOT_FOUND;
    1131             :                         }
    1132         124 :                         continue;
    1133             :                 }
    1134        3905 :                 if (currentLine[0] == '#') {
    1135             :                         /* chunk */
    1136        2909 :                         if (!strncmp("#EXT-X-PART:", currentLine, 12)) {
    1137             :                                 GF_Err e = GF_NON_COMPLIANT_BITSTREAM;
    1138             :                                 char *sep;
    1139         672 :                                 char *file = strstr(currentLine, "URI=\"");
    1140         672 :                                 char *dur = strstr(currentLine, "DURATION=");
    1141         672 :                                 char *br = strstr(currentLine, "BYTERANGE=");
    1142             : 
    1143         672 :                                 if (strstr(currentLine, "INDEPENDENT=YES")) {
    1144         160 :                                         attribs.independent_part = GF_TRUE;
    1145             :                                 }
    1146         672 :                                 if (br) {
    1147         443 :                                         u64 start=0;
    1148         443 :                                         u32 size=0;
    1149         443 :                                         sep = strchr(br, ',');
    1150         443 :                                         if (sep) sep[0] = 0;
    1151         443 :                                         if (sscanf(br+10, "\"%u@"LLU"\"", &size, &start) != 2)
    1152             :                                                 file = NULL;
    1153         443 :                                         attribs.byte_range_start = start;
    1154         443 :                                         attribs.byte_range_end = start + size - 1;
    1155             :                                 }
    1156         672 :                                 if (dur) {
    1157         672 :                                         sep = strchr(dur, ',');
    1158         672 :                                         if (sep) sep[0] = 0;
    1159        1344 :                                         attribs.duration_in_seconds = atof(dur+9);
    1160             :                                 }
    1161             : 
    1162         672 :                                 if (file && dur) {
    1163         672 :                                         file += 5; // file starts with `URI:"`, move to start of URL
    1164             :                                         //find end quote
    1165         672 :                                         sep = strchr(file, '"');
    1166         672 :                                         if (!sep) {
    1167             :                                                 e = GF_NON_COMPLIANT_BITSTREAM;
    1168           0 :                                                 _CLEANUP
    1169             :                                                 return e;
    1170             :                                         }
    1171         672 :                                         sep[0] = 0;
    1172             : 
    1173         672 :                                         attribs.low_latency = GF_TRUE;
    1174         672 :                                         attribs.is_media_segment = GF_TRUE;
    1175         672 :                                         e = declare_sub_playlist(file, baseURL, &attribs, sub_playlist, playlist, in_stream);
    1176             : 
    1177         672 :                                         (*playlist)->low_latency = GF_TRUE;
    1178         672 :                                         sep[0] = '"';
    1179             :                                 }
    1180         672 :                                 attribs.is_media_segment = GF_FALSE;
    1181         672 :                                 attribs.low_latency = GF_FALSE;
    1182         672 :                                 attribs.independent_part = GF_FALSE;
    1183         672 :                                 attribs.byte_range_start = attribs.byte_range_end = 0;
    1184         672 :                                 attribs.duration_in_seconds = 0;
    1185         672 :                                 if (e != GF_OK) {
    1186           0 :                                         _CLEANUP
    1187             :                                         return e;
    1188             :                                 }
    1189             :                         }
    1190             :                         /* A comment or a directive */
    1191        2237 :                         else if (!strncmp("#EXT", currentLine, 4)) {
    1192        2237 :                                 attributes = parse_attributes(currentLine, &attribs);
    1193        2237 :                                 if (attributes == NULL) {
    1194         221 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8]Comment at line %d : %s\n", currentLineNumber, currentLine));
    1195             :                                 } else {
    1196        2016 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] Directive at line %d: \"%s\", attributes=", currentLineNumber, currentLine));
    1197             :                                         i = 0;
    1198        4407 :                                         while (attributes[i] != NULL) {
    1199        2391 :                                                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, (" [%d]='%s'", i, attributes[i]));
    1200        2391 :                                                 gf_free(attributes[i]);
    1201        2391 :                                                 attributes[i] = NULL;
    1202        2391 :                                                 i++;
    1203             :                                         }
    1204        2016 :                                         GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("\n"));
    1205        2016 :                                         gf_free(attributes);
    1206             :                                         attributes = NULL;
    1207             :                                 }
    1208        2237 :                                 if (attribs.is_playlist_ended) {
    1209          33 :                                         (*playlist)->playlist_needs_refresh = GF_FALSE;
    1210             :                                 }
    1211        2237 :                                 if (attribs.independent_segments) {
    1212        1727 :                                         (*playlist)->independent_segments = GF_TRUE;
    1213             :                                 }
    1214        2237 :                                 if (attribs.low_latency) {
    1215          61 :                                         (*playlist)->low_latency = GF_TRUE;
    1216          61 :                                         attribs.low_latency = GF_FALSE;
    1217             :                                 }
    1218        2237 :                                 if (attribs.mediaURL) {
    1219          13 :                                         GF_Err e = declare_sub_playlist(attribs.mediaURL, baseURL, &attribs, sub_playlist, playlist, in_stream);
    1220          13 :                                         gf_free(attribs.mediaURL);
    1221          13 :                                         attribs.mediaURL = NULL;
    1222          13 :                                         if (e != GF_OK) {
    1223           0 :                                                 _CLEANUP
    1224             :                                                 return e;
    1225             :                                         }
    1226             :                                 }
    1227             :                         }
    1228             :                 } else {
    1229             : 
    1230             :                         /*file encountered: sub-playlist or segment*/
    1231         996 :                         GF_Err e = declare_sub_playlist(currentLine, baseURL, &attribs, sub_playlist, playlist, in_stream);
    1232         996 :                         attribs.current_media_seq += 1;
    1233         996 :                         if (e != GF_OK) {
    1234           0 :                                 _CLEANUP
    1235             :                                 return e;
    1236             :                         }
    1237             : 
    1238             :                         //do not reset all attributes but at least set width/height/codecs to NULL, otherwise we may miss detection
    1239             :                         //of audio-only playlists in av sequences
    1240             : 
    1241         996 :                         reset_attribs(&attribs);
    1242             :                 }
    1243             :         }
    1244             : 
    1245         124 :         _CLEANUP
    1246             : 
    1247             : #undef _CLEANUP
    1248             : 
    1249         137 :         for (i=0; i<(int)gf_list_count((*playlist)->streams); i++) {
    1250             :                 u32 j;
    1251         137 :                 Stream *prog = gf_list_get((*playlist)->streams, i);
    1252         137 :                 prog->computed_duration = 0;
    1253         292 :                 for (j=0; j<gf_list_count(prog->variants); j++) {
    1254         155 :                         PlaylistElement *ple = gf_list_get(prog->variants, j);
    1255         155 :                         if (ple->element_type == TYPE_PLAYLIST) {
    1256         155 :                                 if (ple->element.playlist.computed_duration > prog->computed_duration)
    1257         102 :                                         prog->computed_duration = ple->element.playlist.computed_duration;
    1258             :                         }
    1259             :                 }
    1260             : 
    1261             :         }
    1262             : 
    1263         124 :         if (attribs.version < attribs.compatibility_version) {
    1264           0 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] Version %d specified but tags from version %d detected\n", attribs.version, attribs.compatibility_version));
    1265             :         }
    1266             :         return GF_OK;
    1267             : }

Generated by: LCOV version 1.13