GF_Err parse_sub_playlist(const char * file, VariantPlaylist ** playlist, const char * baseURL, Program * in_program, PlaylistElement *sub_playlist) { int len, i, currentLineNumber; FILE * f=NULL; char *m3u8_payload; u32 m3u8_size, m3u8pos; VariantPlaylist * pl; char currentLine[M3U8_BUF_SIZE]; char ** attributes = NULL; s_accumulated_attributes attribs; if (!strncmp(file, "gmem://", 7)) { if (sscanf(file, "gmem://%d@%p", &m3u8_size, &m3u8_payload) != 2) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot Open m3u8 source %s for reading\n", file)); return GF_SERVICE_ERROR; } } else { f = gf_f64_open(file, "rt"); if (!f) { GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot Open m3u8 file %s for reading\n", file)); return GF_SERVICE_ERROR; } } if (*playlist == NULL) { *playlist = variant_playlist_new(); if (!(*playlist)) { if (f) fclose(f); return GF_OUT_OF_MEM; } } pl = *playlist; currentLineNumber = 0; bzero(&attribs, sizeof(s_accumulated_attributes)); attribs.bandwidth = 0; attribs.durationInSeconds = 0; attribs.targetDurationInSeconds = 0; attribs.isVariantPlaylist = 0; attribs.isPlaylistEnded = 0; attribs.minMediaSequence = 0; attribs.currentMediaSequence = 0; m3u8pos=0; while (1) { char * eof; if (f) { if (!fgets(currentLine, sizeof(currentLine), f)) break; } else { u32 __idx=0; if (m3u8pos>=m3u8_size) break; while (1) { currentLine[__idx] = m3u8_payload[m3u8pos]; __idx++; m3u8pos++; if ((currentLine[__idx-1]=='\n') || (currentLine[__idx-1]=='\r')) { currentLine[__idx]=0; break; } } } currentLineNumber++; eof = strchr(currentLine, '\r'); if (eof) eof[0] = '\0'; eof = strchr(currentLine, '\n'); if (eof) eof[0] = '\0'; len = strlen( currentLine); if (len < 1) continue; if (currentLineNumber == 1) { /* Playlist MUST start with #EXTM3U */ /* if (len < 7 || strncmp("#EXTM3U", currentLine, 7)!=0) { fclose(f); variant_playlist_del(pl); GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Failed to parse M3U8 File, it should start with #EXTM3U, but was : %s\n", currentLine)); return GF_STREAM_NOT_FOUND; } continue; */ } if (currentLine[0] == '#') { /* A comment or a directive */ if (strncmp("#EXT", currentLine, 4)==0) { attributes = parseAttributes(currentLine, &attribs); if (attributes == NULL) { MYLOG(("Comment at line %d : %s\n", currentLineNumber, currentLine)); } else { MYLOG(("Directive at line %d: \"%s\", attributes=", currentLineNumber, currentLine)); i = 0; while (attributes[i] != NULL) { MYLOG((" [%d]='%s'", i, attributes[i])); gf_free(attributes[i]); attributes[i] = NULL; i++; } MYLOG(("\n")); gf_free(attributes); attributes = NULL; } if (attribs.isPlaylistEnded) { pl->playlistNeedsRefresh = 0; } } } else { char * fullURL = currentLine; if (gf_url_is_local(currentLine)) { /* if (gf_url_is_local(baseURL)){ int num_chars = -1; if (baseURL[strlen(baseURL)-1] == '/'){ num_chars = asprintf(&fullURL, "%s%s", baseURL, currentLine); } else { num_chars = asprintf(&fullURL, "%s/%s", baseURL, currentLine); } if (num_chars < 0 || fullURL == NULL){ variant_playlist_del(*playlist); playlist = NULL; return GF_OUT_OF_MEM; } } else */ { fullURL = gf_url_concatenate(baseURL, currentLine); } assert( fullURL ); } { u32 count; PlaylistElement * currentPlayList = sub_playlist; /* First, we have to find the matching program */ Program * program = in_program; if (!in_program) program = variant_playlist_find_matching_program(pl, attribs.programId); /* We did not found the program, we create it */ if (program == NULL) { program = program_new(attribs.programId); if (program == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); if (f) fclose(f); playlist = NULL; return GF_OUT_OF_MEM; } gf_list_add(pl->programs, program); if (pl->currentProgram < 0) pl->currentProgram = program->programId; } /* OK, we have a program, we have to choose the elements with same bandwidth */ assert( program ); assert( program->bitrates); count = gf_list_count( program->bitrates); if (!currentPlayList) { for (i = 0; i < (s32) count; i++) { PlaylistElement * itPlayListElement = gf_list_get(program->bitrates, i); assert( itPlayListElement ); if (itPlayListElement->bandwidth == attribs.bandwidth) { currentPlayList = itPlayListElement; break; } } } if (attribs.isVariantPlaylist) { /* We are the Variant Playlist */ if (currentPlayList != NULL) { /* should not happen, it means we redefine something previsouly added */ //assert( 0 ); } currentPlayList = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } assert( fullURL); currentPlayList->url = gf_strdup(fullURL); currentPlayList->title = attribs.title ? gf_strdup(attribs.title):NULL; currentPlayList->codecs = attribs.codecs ? gf_strdup(attribs.codecs):NULL; gf_list_add(program->bitrates, currentPlayList); currentPlayList->width = attribs.width; currentPlayList->height = attribs.height; } else { /* Normal Playlist */ assert( pl->programs); if (currentPlayList == NULL) { /* This is in facts a "normal" playlist without any element in it */ PlaylistElement * subElement; assert(baseURL); currentPlayList = playlist_element_new( TYPE_PLAYLIST, baseURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } assert(currentPlayList->element.playlist.elements); assert( fullURL); assert( currentPlayList->url); currentPlayList->title = NULL; currentPlayList->codecs = NULL; subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); gf_list_add(program->bitrates, currentPlayList); currentPlayList->element.playlist.computed_duration += subElement->durationInfo; assert( program ); assert( program->bitrates); assert( currentPlayList); } else { PlaylistElement * subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds, attribs.byteRangeStart, attribs.byteRangeEnd); if (currentPlayList->elementType != TYPE_PLAYLIST) { currentPlayList->elementType = TYPE_PLAYLIST; if (!currentPlayList->element.playlist.elements) currentPlayList->element.playlist.elements = gf_list_new(); } if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; if (f) fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); currentPlayList->element.playlist.computed_duration += subElement->durationInfo; } } currentPlayList->element.playlist.currentMediaSequence = attribs.currentMediaSequence ; /* We first set the default duration for element, aka targetDuration */ if (attribs.targetDurationInSeconds > 0) { currentPlayList->element.playlist.target_duration = attribs.targetDurationInSeconds; currentPlayList->durationInfo = attribs.targetDurationInSeconds; } if (attribs.durationInSeconds) { if (currentPlayList->durationInfo == 0) { /* we set the playlist duration info as the duration of a segment, only if it's not set There are cases of playlist with the last segment with a duration different from the others (example: Apple bipbop test)*/ currentPlayList->durationInfo = attribs.durationInSeconds; } } currentPlayList->element.playlist.mediaSequenceMin = attribs.minMediaSequence; currentPlayList->element.playlist.mediaSequenceMax = attribs.currentMediaSequence++; if (attribs.bandwidth > 1) currentPlayList->bandwidth = attribs.bandwidth; if (attribs.isPlaylistEnded) currentPlayList->element.playlist.is_ended = 1; } /* Cleanup all line-specific fields */ if (attribs.title) { gf_free(attribs.title); attribs.title = NULL; } attribs.durationInSeconds = 0; attribs.bandwidth = 0; attribs.programId = 0; if (attribs.codecs != NULL) { gf_free(attribs.codecs); attribs.codecs = NULL; } if (fullURL != currentLine) { gf_free(fullURL); } } } if (f) fclose(f); for (i=0; i < (int) gf_list_count(pl->programs); i++) { u32 j; Program *prog = gf_list_get(pl->programs, i); prog->computed_duration = 0; for (j=0; j<gf_list_count(prog->bitrates); j++) { PlaylistElement *ple = gf_list_get(prog->bitrates, j); if (ple->elementType==TYPE_PLAYLIST) { if (ple->element.playlist.computed_duration > prog->computed_duration) prog->computed_duration = ple->element.playlist.computed_duration; } } } return GF_OK; }
GF_Err parse_sub_playlist(const char * file, VariantPlaylist ** playlist, const char * baseURL, Program * in_program, PlaylistElement *sub_playlist) { int readen, readPointer, len, i, currentLineNumber; FILE * f; VariantPlaylist * pl; char currentLine[M3U8_BUF_SIZE]; char ** attributes = NULL; s_accumulated_attributes attribs; f = gf_f64_open(file, "rt"); if (!f) { GF_LOG( GF_LOG_ERROR, GF_LOG_CONTAINER,("[M3U8] Cannot Open m3u8 file %s for reading\n", file)); return GF_SERVICE_ERROR; } if (*playlist == NULL) { *playlist = variant_playlist_new(); if (!(*playlist)) { fclose(f); return GF_OUT_OF_MEM; } } pl = *playlist; readen=0; readPointer = 0; currentLineNumber = 0; bzero(&attribs, sizeof(s_accumulated_attributes)); attribs.bandwidth = 0; attribs.durationInSeconds = 0; attribs.targetDurationInSeconds = 0; attribs.isVariantPlaylist = 0; attribs.isPlaylistEnded = 0; attribs.minMediaSequence = 0; attribs.currentMediaSequence = 0; while (fgets(currentLine, sizeof(currentLine), f)) { char * eof; currentLineNumber++; eof = strchr(currentLine, '\r'); if (eof) eof[0] = '\0'; eof = strchr(currentLine, '\n'); if (eof) eof[0] = '\0'; len = strlen( currentLine); if (len < 1) continue; if (currentLineNumber == 1) { /* Playlist MUST start with #EXTM3U */ if (len < 7 || strncmp("#EXTM3U", currentLine, 7)!=0) { fclose(f); variant_playlist_del(pl); GF_LOG( GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to parse M3U8 File, it should start with #EXTM3U, but was : %s\n", currentLine)); return GF_STREAM_NOT_FOUND; } continue; } if (currentLine[0] == '#') { /* A comment or a directive */ if (strncmp("#EXT", currentLine, 4)==0) { attributes = parseAttributes(currentLine, &attribs); if (attributes == NULL) { MYLOG(("Comment at line %d : %s\n", currentLineNumber, currentLine)); } else { MYLOG(("Directive at line %d: \"%s\", attributes=", currentLineNumber, currentLine)); i = 0; while (attributes[i] != NULL) { MYLOG((" [%d]='%s'", i, attributes[i])); gf_free(attributes[i]); attributes[i] = NULL; i++; } MYLOG(("\n")); gf_free(attributes); attributes = NULL; } if (attribs.isPlaylistEnded) { pl->playlistNeedsRefresh = 0; } } } else { char * fullURL = currentLine; //printf("Line %d: '%s'\n", currentLineNumber, currentLine); if (gf_url_is_local(currentLine)) { /* if (gf_url_is_local(baseURL)){ int num_chars = -1; if (baseURL[strlen(baseURL)-1] == '/'){ num_chars = asprintf(&fullURL, "%s%s", baseURL, currentLine); } else { num_chars = asprintf(&fullURL, "%s/%s", baseURL, currentLine); } if (num_chars < 0 || fullURL == NULL){ variant_playlist_del(*playlist); playlist = NULL; return GF_OUT_OF_MEM; } } else */ { fullURL = gf_url_concatenate(baseURL, currentLine); } assert( fullURL ); /*printf("*** calculated full path = %s from %s and %s\n", fullURL, currentLine, baseURL);*/ } { u32 count; PlaylistElement * currentPlayList = sub_playlist; /* First, we have to find the matching program */ Program * program = in_program; if (!in_program) program = variant_playlist_find_matching_program(pl, attribs.programId); /* We did not found the program, we create it */ if (program == NULL) { program = program_new(attribs.programId); if (program == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); fclose(f); playlist = NULL; return GF_OUT_OF_MEM; } gf_list_add(pl->programs, program); if (pl->currentProgram < 0) pl->currentProgram = program->programId; } /* OK, we have a program, we have to choose the elements with same bandwidth */ assert( program ); assert( program->bitrates); count = gf_list_count( program->bitrates); if (!currentPlayList) { for (i = 0; i < (s32) count; i++) { PlaylistElement * itPlayListElement = gf_list_get(program->bitrates, i); assert( itPlayListElement ); if (itPlayListElement->bandwidth == attribs.bandwidth) { currentPlayList = itPlayListElement; break; } } } if (attribs.isVariantPlaylist) { /* We are the Variant Playlist */ if (currentPlayList != NULL) { /* should not happen, it means we redefine something previsouly added */ //assert( 0 ); } currentPlayList = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } assert( fullURL); currentPlayList->url = gf_strdup(fullURL); currentPlayList->title = attribs.title ? gf_strdup(attribs.title):NULL; currentPlayList->codecs = attribs.codecs ? gf_strdup(attribs.codecs):NULL; gf_list_add(program->bitrates, currentPlayList); } else { /* Normal Playlist */ assert( pl->programs); if (currentPlayList == NULL) { /* This is in facts a "normal" playlist without any element in it */ PlaylistElement * subElement; assert(baseURL); currentPlayList = playlist_element_new( TYPE_PLAYLIST, baseURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (currentPlayList == NULL) { /* OUT of memory */ variant_playlist_del(*playlist); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } assert(currentPlayList->element.playlist.elements); assert( fullURL); assert( currentPlayList->url); currentPlayList->title = NULL; currentPlayList->codecs = NULL; subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); gf_list_add(program->bitrates, currentPlayList); assert( program ); assert( program->bitrates); assert( currentPlayList); } else { PlaylistElement * subElement = playlist_element_new( TYPE_UNKNOWN, fullURL, attribs.title, attribs.codecs, attribs.durationInSeconds); if (currentPlayList->elementType != TYPE_PLAYLIST) { currentPlayList->elementType = TYPE_PLAYLIST; if (!currentPlayList->element.playlist.elements) currentPlayList->element.playlist.elements = gf_list_new(); } if (subElement == NULL) { variant_playlist_del(*playlist); playlist_element_del(currentPlayList); playlist = NULL; fclose(f); return GF_OUT_OF_MEM; } gf_list_add(currentPlayList->element.playlist.elements, subElement); } } currentPlayList->element.playlist.currentMediaSequence = attribs.currentMediaSequence ; /* We first set the default duration for element, aka targetDuration */ if (attribs.targetDurationInSeconds > 0) { currentPlayList->element.playlist.target_duration = attribs.targetDurationInSeconds; currentPlayList->durationInfo = attribs.targetDurationInSeconds; } if (attribs.durationInSeconds) { currentPlayList->durationInfo = attribs.durationInSeconds; } currentPlayList->element.playlist.mediaSequenceMin = attribs.minMediaSequence; currentPlayList->element.playlist.mediaSequenceMax = attribs.currentMediaSequence++; if (attribs.bandwidth > 1) currentPlayList->bandwidth = attribs.bandwidth; if (attribs.isPlaylistEnded) currentPlayList->element.playlist.is_ended = 1; } /* Cleanup all line-specific fields */ if (attribs.title) { gf_free(attribs.title); attribs.title = NULL; } attribs.durationInSeconds = 0; attribs.bandwidth = 0; attribs.programId = 0; if (attribs.codecs != NULL) { gf_free(attribs.codecs); attribs.codecs = NULL; } if (fullURL != currentLine) { gf_free(fullURL); } } } fclose(f); return GF_OK; }