Skip to content

Commit d3583ba

Browse files
committed
added extra segment when custom tags are lingering at the end of the playlist
1 parent c619051 commit d3583ba

File tree

5 files changed

+382
-51
lines changed

5 files changed

+382
-51
lines changed

src/hlsparse.c

100755100644
+45
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "parse.h"
22
#include <memory.h>
3+
#include <stdio.h>
34

45
hlsparse_malloc_callback hls_malloc = (hlsparse_malloc_callback) malloc;
56
hlsparse_free_callback hls_free = (hlsparse_free_callback) free;
@@ -138,5 +139,49 @@ int hlsparse_media_playlist(const char *src, size_t size, media_playlist_t *dest
138139
res = pt - src;
139140
}
140141

142+
// custom tags can exist after the last segment for things like pre-roll ad insertion
143+
// create a zero length segment and attach these custom tags to that segment
144+
string_list_t *tag_media = &(dest->custom_tags);
145+
if(tag_media && tag_media->data)
146+
{
147+
// create a new segment
148+
segment_t *segment = hls_malloc(sizeof(segment_t));
149+
hlsparse_segment_init(segment);
150+
151+
// add the new segment to the playlist
152+
segment_list_t *next = &dest->segments;
153+
while(next) {
154+
if(!next->data) {
155+
next->data = segment;
156+
break;
157+
} else if(!next->next) {
158+
next->next = hls_malloc(sizeof(segment_list_t));
159+
hlsparse_segment_list_init(next->next);
160+
next->next->data = segment;
161+
break;
162+
}
163+
next = next->next;
164+
};
165+
166+
dest->last_segment = segment;
167+
++(dest->nb_segments);
168+
169+
segment->key_index = dest->nb_keys - 1;
170+
segment->map_index = dest->nb_maps - 1;
171+
segment->daterange_index = dest->nb_dateranges - 1;
172+
173+
segment->pdt = segment->pdt_end = dest->next_segment_pdt;
174+
segment->sequence_num = dest->next_segment_media_sequence;
175+
176+
segment->custom_tags.data = dest->custom_tags.data;
177+
segment->custom_tags.next = dest->custom_tags.next;
178+
dest->custom_tags.data = NULL;
179+
dest->custom_tags.next = NULL;
180+
181+
segment->discontinuity = dest->next_segment_discontinuity;
182+
// reset the discontinuity flag
183+
dest->next_segment_discontinuity = HLS_FALSE;
184+
}
185+
141186
return res;
142187
}

src/parse_playlist.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ int parse_media_playlist_tag(const char *src, size_t size, media_playlist_t *des
305305
next->data = segment;
306306
break;
307307
} else if(!next->next) {
308-
next->next = hls_malloc(sizeof(segment_t));
308+
next->next = hls_malloc(sizeof(segment_list_t));
309309
hlsparse_segment_list_init(next->next);
310310
next->next->data = segment;
311311
break;

src/write.c

+57-50
Original file line numberDiff line numberDiff line change
@@ -260,68 +260,75 @@ HLSCode hlswrite_media(char **dest, int *dest_size, media_playlist_t *playlist)
260260

261261
for(i=0; i<playlist->nb_segments; ++i)
262262
{
263-
// write custom tags first
263+
// write custom tags first even if the segment uri isn't available
264+
// because we could have custom tags indicating actions here e.g. ad post-roll injection
264265
string_list_t *ctags = &seg->data->custom_tags;
265266
while(ctags && ctags->data) {
266267
ADD_TAG(ctags->data);
267268
ctags = ctags->next;
268269
}
269-
270-
// new Key index?
271-
if(seg->data->key_index > key_idx) {
272-
key_idx = seg->data->key_index;
273-
// find the key
274-
int j=0;
275-
key_list_t *key_list = &playlist->keys;
276-
hls_key_t *key = NULL;
277-
while(key_list && key_list->data && j++ <= key_idx) {
278-
key = key_list->data;
279-
key_list = key_list->next;
280-
}
281270

282-
// add key tag
283-
if(key) {
284-
switch(key->method) {
285-
case KEY_METHOD_NONE: START_TAG_ENUM(EXTXKEY, METHOD, NONE); break;
286-
case KEY_METHOD_AES128: START_TAG_ENUM(EXTXKEY, METHOD, AES128); break;
287-
case KEY_METHOD_SAMPLEAES: START_TAG_ENUM(EXTXKEY, METHOD, SAMPLEAES); break;
271+
// only write the other tags if the uri exists
272+
if(seg->data->uri) {
273+
274+
// new Key index?
275+
if(seg->data->key_index > key_idx) {
276+
key_idx = seg->data->key_index;
277+
// find the key
278+
int j=0;
279+
key_list_t *key_list = &playlist->keys;
280+
hls_key_t *key = NULL;
281+
while(key_list && key_list->data && j++ <= key_idx) {
282+
key = key_list->data;
283+
key_list = key_list->next;
288284
}
289-
if(playlist->uri) {
290-
const char *uri = find_relative_path(key->uri, playlist->uri);
291-
ADD_PARAM_STR_OPTL(URI, uri);
292-
}else{
293-
ADD_PARAM_STR_OPTL(URI, key->uri);
285+
286+
// add key tag
287+
if(key) {
288+
switch(key->method) {
289+
case KEY_METHOD_NONE: START_TAG_ENUM(EXTXKEY, METHOD, NONE); break;
290+
case KEY_METHOD_AES128: START_TAG_ENUM(EXTXKEY, METHOD, AES128); break;
291+
case KEY_METHOD_SAMPLEAES: START_TAG_ENUM(EXTXKEY, METHOD, SAMPLEAES); break;
292+
}
293+
if(playlist->uri) {
294+
const char *uri = find_relative_path(key->uri, playlist->uri);
295+
ADD_PARAM_STR_OPTL(URI, uri);
296+
}else{
297+
ADD_PARAM_STR_OPTL(URI, key->uri);
298+
}
299+
ADD_PARAM_HEX_OPTL(KEY_IV, key->iv, 16);
300+
ADD_PARAM_STR_OPTL(KEYFORMAT, key->key_format);
301+
ADD_PARAM_STR_OPTL(KEYFORMATVERSIONS, key->key_format_versions);
302+
END_TAG();
294303
}
295-
ADD_PARAM_HEX_OPTL(KEY_IV, key->iv, 16);
296-
ADD_PARAM_STR_OPTL(KEYFORMAT, key->key_format);
297-
ADD_PARAM_STR_OPTL(KEYFORMATVERSIONS, key->key_format_versions);
298-
END_TAG();
299304
}
300-
}
301305

302-
if(seg->data->discontinuity == HLS_TRUE) {
303-
ADD_TAG(EXTXDISCONTINUITY);
304-
char buf[30];
305-
timestamp_to_iso_date(seg->data->pdt, buf, 30);
306-
ADD_TAG_ENUM(EXTXPROGRAMDATETIME, buf);
307-
}
308-
if(seg->data->byte_range.n > 0) {
309-
if(seg->data->byte_range.o != 0) {
310-
latest = pgprintf(latest, "#%s:%d@%d\n", EXTXBYTERANGE, seg->data->byte_range.n, seg->data->byte_range.o);
306+
if(seg->data->discontinuity == HLS_TRUE) {
307+
ADD_TAG(EXTXDISCONTINUITY);
308+
char buf[30];
309+
timestamp_to_iso_date(seg->data->pdt, buf, 30);
310+
ADD_TAG_ENUM(EXTXPROGRAMDATETIME, buf);
311+
}
312+
313+
if(seg->data->byte_range.n > 0) {
314+
if(seg->data->byte_range.o != 0) {
315+
latest = pgprintf(latest, "#%s:%d@%d\n", EXTXBYTERANGE, seg->data->byte_range.n, seg->data->byte_range.o);
316+
}else{
317+
latest = pgprintf(latest, "#%s:%d\n", EXTXBYTERANGE, seg->data->byte_range.n);
318+
}
319+
}
320+
321+
if(seg->data->title) {
322+
latest = pgprintf(latest, "#%s:%.3f,%s\n", EXTINF, seg->data->duration, seg->data->title);
311323
}else{
312-
latest = pgprintf(latest, "#%s:%d\n", EXTXBYTERANGE, seg->data->byte_range.n);
324+
latest = pgprintf(latest, "#%s:%.3f,\n", EXTINF, seg->data->duration);
325+
}
326+
if(playlist->uri) {
327+
const char *uri = find_relative_path(seg->data->uri, playlist->uri);
328+
ADD_URI(uri);
329+
}else{
330+
ADD_URI(seg->data->uri);
313331
}
314-
}
315-
if(seg->data->title) {
316-
latest = pgprintf(latest, "#%s:%.3f,%s\n", EXTINF, seg->data->duration, seg->data->title);
317-
}else{
318-
latest = pgprintf(latest, "#%s:%.3f,\n", EXTINF, seg->data->duration);
319-
}
320-
if(playlist->uri) {
321-
const char *uri = find_relative_path(seg->data->uri, playlist->uri);
322-
ADD_URI(uri);
323-
}else{
324-
ADD_URI(seg->data->uri);
325332
}
326333
seg = seg->next;
327334
}

test/parserPlaylistTests.c

+151
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,156 @@ void media_playlist_parse_test2(void)
546546

547547
hlsparse_media_playlist_term(&playlist);
548548
}
549+
550+
void media_playlist_parse_test3(void)
551+
{
552+
media_playlist_t playlist;
553+
hlsparse_media_playlist_init(&playlist);
554+
555+
const char *src = "#EXTM3U\n\
556+
#EXT-X-VERSION:3\n\
557+
#EXT-X-TARGETDURATION:6\n\
558+
#EXT-X-MEDIA-SEQUENCE:1\n\
559+
#EXT-X-PLAYLIST-TYPE:VOD\n\
560+
#EXT-X-PROGRAM-DATE-TIME:2018-08-11T21:42:39.900Z\n\
561+
#EXT-X-ASSET-START:id=987,pop=\n\
562+
#EXT-X-KEY:METHOD=AES-128,URI=\"https://key-service.com/key?id=123\",IV=0x3CEA3CC17B919213B8E7CFB5D10D4CAE\n\
563+
#EXTINF:0.033,\n\
564+
ADAP/00060/1001_ADAP_00001.ts\n\
565+
#EXT-X-KEY:METHOD=NONE\n\
566+
#EXT-X-CUE-OUT:_params=\"abc=d&efg=MIDROLL&pop=1\"\n\
567+
#EXT-X-CUE-IN\n\
568+
#EXT-X-KEY:METHOD=AES-128,URI=\"https://key-service.com/key?id=124\",IV=0x3CEA3CC17B919213B8E7CFB5D10D4CAF\n\
569+
#EXTINF:4.972,\n\
570+
ADAP/00060/1001_ADAP_00002.ts\n\
571+
#EXTINF:5.005,\n\
572+
ADAP/00060/1001_ADAP_00003.ts\n\
573+
#EXTINF:4.605,\n\
574+
ADAP/00060/1001_ADAP_00004.ts\n\
575+
#EXT-X-KEY:METHOD=NONE\n\
576+
#EXT-X-CUE-OUT:_fw_params=\"abc=a&efg=POSTROLL&pop=4\"\n\
577+
#EXT-X-CUE-IN\n\
578+
#EXT-X-ENDLIST\n";
579+
580+
int res = hlsparse_media_playlist(src, strlen(src), &playlist);
581+
CU_ASSERT_EQUAL(res, strlen(src))
582+
CU_ASSERT_EQUAL(playlist.m3u, HLS_TRUE);
583+
CU_ASSERT_EQUAL(playlist.target_duration, 6);
584+
CU_ASSERT_EQUAL(playlist.media_sequence, 1);
585+
CU_ASSERT_EQUAL(playlist.discontinuity_sequence, 0);
586+
CU_ASSERT_EQUAL(playlist.playlist_type, PLAYLIST_TYPE_VOD);
587+
CU_ASSERT_EQUAL(playlist.independent_segments, HLS_FALSE);
588+
CU_ASSERT_EQUAL(playlist.iframes_only, HLS_FALSE);
589+
CU_ASSERT_EQUAL(playlist.start.time_offset, 0.f);
590+
CU_ASSERT_EQUAL(playlist.start.precise, HLS_FALSE);
591+
CU_ASSERT_EQUAL(playlist.nb_segments, 5);
592+
CU_ASSERT_EQUAL(playlist.nb_maps, 0);
593+
CU_ASSERT_EQUAL(playlist.nb_dateranges, 0);
594+
CU_ASSERT_EQUAL(playlist.end_list, HLS_TRUE);
595+
596+
segment_list_t *seg = &playlist.segments;
597+
CU_ASSERT_NOT_EQUAL(seg->data, NULL);
598+
assert_string_equal(seg->data->uri, "ADAP/00060/1001_ADAP_00001.ts", __func__, __LINE__);
599+
CU_ASSERT_EQUAL(seg->data->sequence_num, 0);
600+
CU_ASSERT_EQUAL(seg->data->key_index, 0);
601+
CU_ASSERT_EQUAL(seg->data->map_index, -1);
602+
CU_ASSERT_EQUAL(seg->data->daterange_index, -1);
603+
CU_ASSERT_EQUAL(seg->data->title, NULL);
604+
CU_ASSERT_EQUAL(seg->data->discontinuity, HLS_FALSE);
605+
CU_ASSERT_EQUAL(seg->data->pdt_discontinuity, HLS_FALSE);
606+
CU_ASSERT_EQUAL(seg->data->pdt, 1534023759900);
607+
CU_ASSERT_EQUAL(seg->data->pdt_end, 1534023759933);
608+
CU_ASSERT_EQUAL(seg->data->duration, 0.033f);
609+
CU_ASSERT_EQUAL(seg->data->byte_range.n, 0);
610+
CU_ASSERT_EQUAL(seg->data->byte_range.o, 0);
611+
CU_ASSERT_EQUAL(seg->data->byte_range.o, 0);
612+
CU_ASSERT_NOT_EQUAL(seg->data->custom_tags.data, NULL);
613+
assert_string_equal(seg->data->custom_tags.data, "EXT-X-ASSET-START:id=987,pop=", __func__, __LINE__);
614+
CU_ASSERT_EQUAL(seg->data->custom_tags.next, NULL);
615+
616+
seg = seg->next;
617+
CU_ASSERT_NOT_EQUAL(seg->data, NULL);
618+
assert_string_equal(seg->data->uri, "ADAP/00060/1001_ADAP_00002.ts", __func__, __LINE__);
619+
CU_ASSERT_EQUAL(seg->data->sequence_num, 1);
620+
CU_ASSERT_EQUAL(seg->data->key_index, 2);
621+
CU_ASSERT_EQUAL(seg->data->map_index, -1);
622+
CU_ASSERT_EQUAL(seg->data->daterange_index, -1);
623+
CU_ASSERT_EQUAL(seg->data->title, NULL);
624+
CU_ASSERT_EQUAL(seg->data->discontinuity, HLS_FALSE);
625+
CU_ASSERT_EQUAL(seg->data->pdt_discontinuity, HLS_FALSE);
626+
CU_ASSERT_EQUAL(seg->data->pdt, 1534023759933);
627+
CU_ASSERT_EQUAL(seg->data->pdt_end, 1534023764905);
628+
CU_ASSERT_EQUAL(seg->data->duration, 4.972f);
629+
CU_ASSERT_EQUAL(seg->data->byte_range.n, 0);
630+
CU_ASSERT_EQUAL(seg->data->byte_range.o, 0);
631+
CU_ASSERT_NOT_EQUAL(seg->data->custom_tags.data, NULL);
632+
assert_string_equal(seg->data->custom_tags.data, "EXT-X-CUE-OUT:_params=\"abc=d&efg=MIDROLL&pop=1\"", __func__, __LINE__);
633+
CU_ASSERT_NOT_EQUAL(seg->data->custom_tags.next, NULL);
634+
assert_string_equal(seg->data->custom_tags.next->data, "EXT-X-CUE-IN", __func__, __LINE__);
635+
CU_ASSERT_EQUAL(seg->data->custom_tags.next->next, NULL);
636+
637+
seg = seg->next;
638+
CU_ASSERT_NOT_EQUAL(seg->data, NULL);
639+
assert_string_equal(seg->data->uri, "ADAP/00060/1001_ADAP_00003.ts", __func__, __LINE__);
640+
CU_ASSERT_EQUAL(seg->data->sequence_num, 2);
641+
CU_ASSERT_EQUAL(seg->data->key_index, 2);
642+
CU_ASSERT_EQUAL(seg->data->map_index, -1);
643+
CU_ASSERT_EQUAL(seg->data->daterange_index, -1);
644+
CU_ASSERT_EQUAL(seg->data->title, NULL);
645+
CU_ASSERT_EQUAL(seg->data->discontinuity, HLS_FALSE);
646+
CU_ASSERT_EQUAL(seg->data->pdt_discontinuity, HLS_FALSE);
647+
CU_ASSERT_EQUAL(seg->data->pdt, 1534023764905);
648+
CU_ASSERT_EQUAL(seg->data->pdt_end, 1534023769910);
649+
CU_ASSERT_EQUAL(seg->data->duration, 5.005f);
650+
CU_ASSERT_EQUAL(seg->data->byte_range.n, 0);
651+
CU_ASSERT_EQUAL(seg->data->byte_range.o, 0);
652+
CU_ASSERT_EQUAL(seg->data->custom_tags.data, NULL);
653+
CU_ASSERT_EQUAL(seg->data->custom_tags.next, NULL);
654+
655+
seg = seg->next;
656+
CU_ASSERT_NOT_EQUAL(seg->data, NULL);
657+
assert_string_equal(seg->data->uri, "ADAP/00060/1001_ADAP_00004.ts", __func__, __LINE__);
658+
CU_ASSERT_EQUAL(seg->data->sequence_num, 3);
659+
CU_ASSERT_EQUAL(seg->data->key_index, 2);
660+
CU_ASSERT_EQUAL(seg->data->map_index, -1);
661+
CU_ASSERT_EQUAL(seg->data->daterange_index, -1);
662+
CU_ASSERT_EQUAL(seg->data->title, NULL);
663+
CU_ASSERT_EQUAL(seg->data->discontinuity, HLS_FALSE);
664+
CU_ASSERT_EQUAL(seg->data->pdt_discontinuity, HLS_FALSE);
665+
CU_ASSERT_EQUAL(seg->data->pdt, 1534023769910);
666+
CU_ASSERT_EQUAL(seg->data->pdt_end, 1534023774515);
667+
CU_ASSERT_EQUAL(seg->data->duration, 4.605f);
668+
CU_ASSERT_EQUAL(seg->data->byte_range.n, 0);
669+
CU_ASSERT_EQUAL(seg->data->byte_range.o, 0);
670+
CU_ASSERT_EQUAL(seg->data->custom_tags.data, NULL);
671+
CU_ASSERT_EQUAL(seg->data->custom_tags.next, NULL);
672+
673+
seg = seg->next;
674+
CU_ASSERT_NOT_EQUAL(seg->data, NULL);
675+
CU_ASSERT_EQUAL(seg->data->uri, NULL);
676+
CU_ASSERT_EQUAL(seg->data->sequence_num, 4);
677+
CU_ASSERT_EQUAL(seg->data->key_index, 3);
678+
CU_ASSERT_EQUAL(seg->data->map_index, -1);
679+
CU_ASSERT_EQUAL(seg->data->daterange_index, -1);
680+
CU_ASSERT_EQUAL(seg->data->title, NULL);
681+
CU_ASSERT_EQUAL(seg->data->discontinuity, HLS_FALSE);
682+
CU_ASSERT_EQUAL(seg->data->pdt_discontinuity, HLS_FALSE);
683+
CU_ASSERT_EQUAL(seg->data->pdt, 1534023774515);
684+
CU_ASSERT_EQUAL(seg->data->pdt_end, 1534023774515);
685+
CU_ASSERT_EQUAL(seg->data->duration, 0.f);
686+
CU_ASSERT_EQUAL(seg->data->byte_range.n, 0);
687+
CU_ASSERT_EQUAL(seg->data->byte_range.o, 0);
688+
CU_ASSERT_NOT_EQUAL(seg->data->custom_tags.data, NULL);
689+
assert_string_equal(seg->data->custom_tags.data, "EXT-X-CUE-OUT:_fw_params=\"abc=a&efg=POSTROLL&pop=4\"", __func__, __LINE__);
690+
CU_ASSERT_NOT_EQUAL(seg->data->custom_tags.next, NULL);
691+
assert_string_equal(seg->data->custom_tags.next->data, "EXT-X-CUE-IN", __func__, __LINE__);
692+
CU_ASSERT_EQUAL(seg->data->custom_tags.next->next, NULL);
693+
694+
seg = seg->next;
695+
CU_ASSERT_EQUAL(seg, NULL);
696+
697+
hlsparse_media_playlist_term(&playlist);
698+
}
549699
void setup(void)
550700
{
551701
hlsparse_global_init();
@@ -558,5 +708,6 @@ void setup(void)
558708
test("media_playlist_term", media_playlist_term_test);
559709
test("media_playlist_parse", media_playlist_parse_test);
560710
test("media_playlist_parse2", media_playlist_parse_test2);
711+
test("media_playlist_parse3", media_playlist_parse_test3);
561712
}
562713

0 commit comments

Comments
 (0)