httplive.c 62.7 KB
Newer Older
1
2
3
/*****************************************************************************
 * httplive.c: HTTP Live Streaming stream filter
 *****************************************************************************
4
 * Copyright (C) 2010-2011 M2X BV
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 * $Id$
 *
 * Author: Jean-Paul Saman <jpsaman _AT_ videolan _DOT_ org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

31
#include <limits.h>
32
#include <errno.h>
33

34
35
36
37
38
39
40
41
42
#include <vlc_common.h>
#include <vlc_plugin.h>

#include <assert.h>

#include <vlc_threads.h>
#include <vlc_arrays.h>
#include <vlc_stream.h>
#include <vlc_url.h>
43
#include <vlc_memory.h>
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  Open (vlc_object_t *);
static void Close(vlc_object_t *);

vlc_module_begin()
    set_category(CAT_INPUT)
    set_subcategory(SUBCAT_INPUT_STREAM_FILTER)
    set_description(N_("Http Live Streaming stream filter"))
    set_capability("stream_filter", 20)
    set_callbacks(Open, Close)
vlc_module_end()

/*****************************************************************************
 *
 *****************************************************************************/
typedef struct segment_s
{
    int         sequence;   /* unique sequence number */
65
    int         duration;   /* segment duration (seconds) */
66
    uint64_t    size;       /* segment size in bytes */
67
    uint64_t    bandwidth;  /* bandwidth usage of segments (bits per second)*/
68
69
70
71
72
73
74
75
76
77
78

    vlc_url_t   url;
    vlc_mutex_t lock;
    block_t     *data;      /* data */
} segment_t;

typedef struct hls_stream_s
{
    int         id;         /* program id */
    int         version;    /* protocol version should be 1 */
    int         sequence;   /* media sequence number */
79
    int         duration;   /* maximum duration per segment (s) */
80
    uint64_t    bandwidth;  /* bandwidth usage of segments (bits per second)*/
81
82
    uint64_t    size;       /* stream length is calculated by taking the sum
                               foreach segment of (segment->duration * hls->bandwidth/8) */
83

84
85
    vlc_array_t *segments;  /* list of segments */
    vlc_url_t   url;        /* uri to m3u8 */
86
    vlc_mutex_t lock;
87
88
89
90
91
    bool        b_cache;    /* allow caching */
} hls_stream_t;

struct stream_sys_t
{
92
    vlc_url_t     m3u8;         /* M3U8 url */
93
94
    vlc_thread_t  reload;       /* HLS m3u8 reload thread */
    vlc_thread_t  thread;       /* HLS segment download thread */
95

96
97
    block_t      *peeked;

98
    /* */
99
100
    vlc_array_t  *hls_stream;   /* bandwidth adaptation */
    uint64_t      bandwidth;    /* measured bandwidth (bits per second) */
101

102
103
104
    /* Download */
    struct hls_download_s
    {
105
        int         stream;     /* current hls_stream  */
106
107
108
109
110
111
        int         segment;    /* current segment for downloading */
        int         seek;       /* segment requested by seek (default -1) */
        vlc_mutex_t lock_wait;  /* protect segment download counter */
        vlc_cond_t  wait;       /* some condition to wait on */
    } download;

112
    /* Playback */
113
    struct hls_playback_s
114
115
    {
        uint64_t    offset;     /* current offset in media */
116
        int         stream;     /* current hls_stream  */
117
118
        int         segment;    /* current segment for playback */
    } playback;
119
120

    /* Playlist */
121
122
123
124
125
126
    struct hls_playlist_s
    {
        mtime_t     last;       /* playlist last loaded */
        mtime_t     wakeup;     /* next reload time */
        int         tries;      /* times it was not changed */
    } playlist;
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

    /* state */
    bool        b_cache;    /* can cache files */
    bool        b_meta;     /* meta playlist */
    bool        b_live;     /* live stream? or vod? */
    bool        b_error;    /* parsing error */
};

/****************************************************************************
 * Local prototypes
 ****************************************************************************/
static int  Read   (stream_t *, void *p_read, unsigned int i_read);
static int  Peek   (stream_t *, const uint8_t **pp_peek, unsigned int i_peek);
static int  Control(stream_t *, int i_query, va_list);

142
143
static ssize_t read_M3U8_from_stream(stream_t *s, uint8_t **buffer);
static ssize_t read_M3U8_from_url(stream_t *s, vlc_url_t *url, uint8_t **buffer);
144
static char *ReadLine(uint8_t *buffer, uint8_t **pos, size_t len);
145

146
static int hls_Download(stream_t *s, segment_t *segment);
147

148
static void* hls_Thread(void *);
149
static void* hls_Reload(void *);
150

151
static segment_t *segment_GetSegment(hls_stream_t *hls, int wanted);
152
153
static void segment_Free(segment_t *segment);

154
155
static char *ConstructUrl(vlc_url_t *url);

156
157
158
/****************************************************************************
 *
 ****************************************************************************/
159
160
161
162
163
164
165
166
167
168
169
static const char *const ext[] = {
    "#EXT-X-TARGETDURATION",
    "#EXT-X-MEDIA-SEQUENCE",
    "#EXT-X-KEY",
    "#EXT-X-ALLOW-CACHE",
    "#EXT-X-ENDLIST",
    "#EXT-X-STREAM-INF",
    "#EXT-X-DISCONTINUITY",
    "#EXT-X-VERSION"
};

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
static bool isHTTPLiveStreaming(stream_t *s)
{
    const uint8_t *peek, *peek_end;

    int64_t i_size = stream_Peek(s->p_source, &peek, 46);
    if (i_size < 1)
        return false;

    if (strncasecmp((const char*)peek, "#EXTM3U", 7) != 0)
        return false;

    /* Parse stream and search for
     * EXT-X-TARGETDURATION or EXT-X-STREAM-INF tag, see
     * http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8 */
    peek_end = peek + i_size;
    while(peek <= peek_end)
    {
        if (*peek == '#')
        {
189
190
191
192
193
194
            for (unsigned int i = 0; i < ARRAY_SIZE(ext); i++)
            {
                char *p = strstr((const char*)peek, ext[i]);
                if (p != NULL)
                    return true;
            }
195
196
197
198
199
200
201
202
        }
        peek++;
    };

    return false;
}

/* HTTP Live Streaming */
203
static hls_stream_t *hls_New(vlc_array_t *hls_stream, const int id, const uint64_t bw, const char *uri)
204
205
206
207
208
209
{
    hls_stream_t *hls = (hls_stream_t *)malloc(sizeof(hls_stream_t));
    if (hls == NULL) return NULL;

    hls->id = id;
    hls->bandwidth = bw;
210
    hls->duration = -1;/* unknown */
211
    hls->size = 0;
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    hls->sequence = 0; /* default is 0 */
    hls->version = 1;  /* default protocol version */
    hls->b_cache = true;
    vlc_UrlParse(&hls->url, uri, 0);
    hls->segments = vlc_array_new();
    vlc_array_append(hls_stream, hls);
    vlc_mutex_init(&hls->lock);
    return hls;
}

static void hls_Free(hls_stream_t *hls)
{
    vlc_mutex_destroy(&hls->lock);

    if (hls->segments)
    {
        for (int n = 0; n < vlc_array_count(hls->segments); n++)
        {
            segment_t *segment = (segment_t *)vlc_array_item_at_index(hls->segments, n);
            if (segment) segment_Free(segment);
        }
        vlc_array_destroy(hls->segments);
    }

    vlc_UrlClean(&hls->url);
    free(hls);
    hls = NULL;
}

241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
static hls_stream_t *hls_Copy(hls_stream_t *src, const bool b_cp_segments)
{
    assert(src);
    assert(!b_cp_segments); /* FIXME: copying segments is not implemented */

    hls_stream_t *dst = (hls_stream_t *)malloc(sizeof(hls_stream_t));
    if (dst == NULL) return NULL;

    dst->id = src->id;
    dst->bandwidth = src->bandwidth;
    dst->duration = src->duration;
    dst->size = src->size;
    dst->sequence = src->sequence;
    dst->version = src->version;
    dst->b_cache = src->b_cache;
    char *uri = ConstructUrl(&src->url);
    if (uri == NULL)
    {
        free(dst);
        return NULL;
    }
    vlc_UrlParse(&dst->url, uri, 0);
    if (!b_cp_segments)
        dst->segments = vlc_array_new();
    vlc_mutex_init(&dst->lock);
    return dst;
}

269
static hls_stream_t *hls_Get(vlc_array_t *hls_stream, const int wanted)
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
{
    int count = vlc_array_count(hls_stream);
    if (count <= 0)
        return NULL;
    if ((wanted < 0) || (wanted >= count))
        return NULL;
    return (hls_stream_t *) vlc_array_item_at_index(hls_stream, wanted);
}

static inline hls_stream_t *hls_GetFirst(vlc_array_t *hls_stream)
{
    return (hls_stream_t*) hls_Get(hls_stream, 0);
}

static hls_stream_t *hls_GetLast(vlc_array_t *hls_stream)
{
    int count = vlc_array_count(hls_stream);
    if (count <= 0)
        return NULL;
    count--;
    return (hls_stream_t *) hls_Get(hls_stream, count);
}

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
static hls_stream_t *hls_Find(vlc_array_t *hls_stream, hls_stream_t *hls_new)
{
    int count = vlc_array_count(hls_stream);
    for (int n = 0; n < count; n++)
    {
        hls_stream_t *hls = vlc_array_item_at_index(hls_stream, n);
        if (hls)
        {
            /* compare */
            if ((hls->id == hls_new->id) &&
                (hls->bandwidth == hls_new->bandwidth))
                return hls;
        }
    }
    return NULL;
}

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
static uint64_t hls_GetStreamSize(hls_stream_t *hls)
{
    /* NOTE: Stream size is calculated based on segment duration and
     * HLS stream bandwidth from the .m3u8 file. If these are not correct
     * then the deviation from exact byte size will be big and the seek/
     * progressbar will not behave entirely as one expects. */
    uint64_t size = 0UL;
    int count = vlc_array_count(hls->segments);
    for (int n = 0; n < count; n++)
    {
        segment_t *segment = segment_GetSegment(hls, n);
        if (segment)
        {
            size += (segment->duration * (hls->bandwidth / 8));
        }
    }
    return size;
}

329
/* Segment */
330
static segment_t *segment_New(hls_stream_t* hls, const int duration, const char *uri)
331
332
333
334
335
{
    segment_t *segment = (segment_t *)malloc(sizeof(segment_t));
    if (segment == NULL)
        return NULL;

336
    segment->duration = duration; /* seconds */
337
338
    segment->size = 0; /* bytes */
    segment->sequence = 0;
339
    segment->bandwidth = 0;
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
    vlc_UrlParse(&segment->url, uri, 0);
    segment->data = NULL;
    vlc_array_append(hls->segments, segment);
    vlc_mutex_init(&segment->lock);
    return segment;
}

static void segment_Free(segment_t *segment)
{
    vlc_mutex_destroy(&segment->lock);

    vlc_UrlClean(&segment->url);
    if (segment->data)
        block_Release(segment->data);
    free(segment);
    segment = NULL;
}

358
static segment_t *segment_GetSegment(hls_stream_t *hls, const int wanted)
359
360
361
362
363
364
365
366
367
368
369
{
    assert(hls);

    int count = vlc_array_count(hls->segments);
    if (count <= 0)
        return NULL;
    if ((wanted < 0) || (wanted >= count))
        return NULL;
    return (segment_t *) vlc_array_item_at_index(hls->segments, wanted);
}

370
static segment_t *segment_Find(hls_stream_t *hls, const int sequence)
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
{
    assert(hls);

    int count = vlc_array_count(hls->segments);
    if (count <= 0) return NULL;
    for (int n = 0; n < count; n++)
    {
        segment_t *segment = vlc_array_item_at_index(hls->segments, n);
        if (segment == NULL) break;
        if (segment->sequence == sequence)
            return segment;
    }
    return NULL;
}

386
static int ChooseSegment(stream_t *s, const int current)
387
388
389
390
{
    stream_sys_t *p_sys = (stream_sys_t *)s->p_sys;
    hls_stream_t *hls = hls_Get(p_sys->hls_stream, current);
    if (hls == NULL) return 0;
391

F. Yhuel's avatar
F. Yhuel committed
392
    /* Choose a segment to start which is no closer than
393
394
     * 3 times the target duration from the end of the playlist.
     */
395
    int wanted = 0;
396
    int duration = 0;
397
    int sequence = 0;
398
    int count = vlc_array_count(hls->segments);
399
400
401
    int i = p_sys->b_live ? count - 1 : 0;

    while((i >= 0) && (i < count) && vlc_object_alive(s))
402
403
    {
        segment_t *segment = segment_GetSegment(hls, i);
404
405
406
        assert(segment);

        if (segment->duration > hls->duration)
407
        {
F. Yhuel's avatar
F. Yhuel committed
408
            msg_Err(s, "EXTINF:%d duration is larger than EXT-X-TARGETDURATION:%d",
409
                    segment->duration, hls->duration);
410
        }
411
412
413
414
415
416

        duration += segment->duration;
        if (duration >= 3 * hls->duration)
        {
            /* Start point found */
            wanted = p_sys->b_live ? i : 0;
417
            sequence = segment->sequence;
418
419
420
421
422
423
424
            break;
        }

        if (p_sys->b_live)
            i-- ;
        else
          i++;
425
426
    }

427
    msg_Info(s, "Choose segment %d/%d (sequence=%d)", wanted, count, sequence);
428
    return wanted;
429
430
}

431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
/* Parsing */
static char *parse_Attributes(const char *line, const char *attr)
{
    char *p;
    char *begin = (char *) line;
    char *end = begin + strlen(line);

    /* Find start of attributes */
    if ((p = strchr(begin, ':' )) == NULL)
        return NULL;

    begin = p;
    do
    {
        if (strncasecmp(begin, attr, strlen(attr)) == 0)
        {
            /* <attr>=<value>[,]* */
            p = strchr(begin, ',');
            begin += strlen(attr) + 1;
            if (begin >= end)
                return NULL;
            if (p == NULL) /* last attribute */
                return strndup(begin, end - begin);
            /* copy till ',' */
            return strndup(begin, p - begin);
        }
        begin++;
    } while(begin < end);

    return NULL;
}

463
static char *relative_URI(stream_t *s, const char *uri, const char *path)
464
{
465
466
    stream_sys_t *p_sys = s->p_sys;

467
468
469
470
    char *p = strchr(uri, ':');
    if (p != NULL)
        return NULL;

471
472
473
    if (p_sys->m3u8.psz_path == NULL)
        return NULL;

474
475
476
477
478
    char *psz_path = strdup(p_sys->m3u8.psz_path);
    if (psz_path == NULL) return NULL;
    p = strrchr(psz_path, '/');
    if (p) *p = '\0';

479
    char *psz_uri = NULL;
480
481
    if (p_sys->m3u8.psz_password || p_sys->m3u8.psz_username)
    {
482
        if (asprintf(&psz_uri, "%s://%s:%s@%s:%d%s/%s", p_sys->m3u8.psz_protocol,
483
                     p_sys->m3u8.psz_username, p_sys->m3u8.psz_password,
484
485
                     p_sys->m3u8.psz_host, p_sys->m3u8.i_port,
                     path ? path : psz_path, uri) < 0)
486
            goto fail;
487
488
489
    }
    else
    {
490
491
492
        if (asprintf(&psz_uri, "%s://%s:%d%s/%s", p_sys->m3u8.psz_protocol,
                     p_sys->m3u8.psz_host, p_sys->m3u8.i_port,
                     path ? path : psz_path, uri) < 0)
493
           goto fail;
494
    }
ivoire's avatar
ivoire committed
495
    free(psz_path);
496
    return psz_uri;
497
498
499
500

fail:
    free(psz_path);
    return NULL;
501
502
}

503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
static char *ConstructUrl(vlc_url_t *url)
{
    if ((url->psz_protocol == NULL) ||
        (url->psz_path == NULL))
        return NULL;

    if (url->i_port <= 0)
    {
        if (strncmp(url->psz_protocol, "https", 5) == 0)
            url->i_port = 443;
        else
            url->i_port = 80;
    }

    char *psz_url = NULL;
    if (url->psz_password || url->psz_username)
    {
        if (asprintf(&psz_url, "%s://%s:%s@%s:%d%s",
                     url->psz_protocol,
                     url->psz_username, url->psz_password,
                     url->psz_host, url->i_port, url->psz_path) < 0)
            return NULL;
    }
    else
    {
        if (asprintf(&psz_url, "%s://%s:%d%s",
                     url->psz_protocol,
                     url->psz_host, url->i_port, url->psz_path) < 0)
            return NULL;
    }

    return psz_url;
}

537
static int parse_SegmentInformation(hls_stream_t *hls, char *p_read, int *duration)
538
539
{
    assert(hls);
540
    assert(p_read);
541

542
543
544
545
546
    /* strip of #EXTINF: */
    char *p_next = NULL;
    char *token = strtok_r(p_read, ":", &p_next);
    if (token == NULL)
        return VLC_EGENERIC;
547

548
549
550
551
    /* read duration */
    token = strtok_r(NULL, ",", &p_next);
    if (token == NULL)
        return VLC_EGENERIC;
552

553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
    int value;
    if (hls->version < 3)
    {
       value = strtol(token, NULL, 10);
       if (errno == ERANGE)
       {
           *duration = -1;
           return VLC_EGENERIC;
       }
       *duration = value;
    }
    else
    {
        double d = strtod(token, (char **) NULL);
        if (errno == ERANGE)
        {
            *duration = -1;
            return VLC_EGENERIC;
        }
        if ((d) - ((int)d) >= 0.5)
            value = ((int)d) + 1;
        else
            value = ((int)d);
    }
577
578
579

    /* Ignore the rest of the line */

580
581
582
583
584
585
586
587
    return VLC_SUCCESS;
}

static int parse_AddSegment(stream_t *s, hls_stream_t *hls, const int duration, const char *uri)
{
    assert(hls);
    assert(uri);

588
    /* Store segment information */
589
590
    char *psz_path = NULL;
    if (hls->url.psz_path != NULL)
591
    {
592
        psz_path = strdup(hls->url.psz_path);
593
        if (psz_path == NULL)
594
            return VLC_ENOMEM;
595
596
        char *p = strrchr(psz_path, '/');
        if (p) *p = '\0';
597
598
599
    }
    char *psz_uri = relative_URI(s, uri, psz_path);
    free(psz_path);
600

601
    vlc_mutex_lock(&hls->lock);
602
    segment_t *segment = segment_New(hls, duration, psz_uri ? psz_uri : uri);
603
    if (segment)
604
        segment->sequence = hls->sequence + vlc_array_count(hls->segments) - 1;
605
    vlc_mutex_unlock(&hls->lock);
Brian Kurle's avatar
Brian Kurle committed
606
607

    free(psz_uri);
608
    return segment ? VLC_SUCCESS : VLC_ENOMEM;
609
610
}

611
static int parse_TargetDuration(stream_t *s, hls_stream_t *hls, char *p_read)
612
{
613
614
    assert(hls);

615
    int duration = -1;
616
617
618
619
    int ret = sscanf(p_read, "#EXT-X-TARGETDURATION:%d", &duration);
    if (ret != 1)
    {
        msg_Err(s, "expected #EXT-X-TARGETDURATION:<s>");
620
        return VLC_EGENERIC;
621
622
623
    }

    hls->duration = duration; /* seconds */
624
    return VLC_SUCCESS;
625
626
}

627
static int parse_StreamInformation(stream_t *s, vlc_array_t **hls_stream,
628
                                   hls_stream_t **hls, char *p_read, const char *uri)
629
{
630
631
    int id;
    uint64_t bw;
632
633
    char *attr;

634
635
    assert(*hls == NULL);

636
637
638
639
    attr = parse_Attributes(p_read, "PROGRAM-ID");
    if (attr == NULL)
    {
        msg_Err(s, "#EXT-X-STREAM-INF: expected PROGRAM-ID=<value>");
640
        return VLC_EGENERIC;
641
642
643
644
645
646
647
648
    }
    id = atol(attr);
    free(attr);

    attr = parse_Attributes(p_read, "BANDWIDTH");
    if (attr == NULL)
    {
        msg_Err(s, "#EXT-X-STREAM-INF: expected BANDWIDTH=<value>");
649
        return VLC_EGENERIC;
650
651
652
653
654
655
656
    }
    bw = atoll(attr);
    free(attr);

    if (bw == 0)
    {
        msg_Err(s, "#EXT-X-STREAM-INF: bandwidth cannot be 0");
657
        return VLC_EGENERIC;
658
659
    }

F. Yhuel's avatar
F. Yhuel committed
660
    msg_Info(s, "bandwidth adaptation detected (program-id=%d, bandwidth=%"PRIu64").", id, bw);
661

662
    char *psz_uri = relative_URI(s, uri, NULL);
663

664
    *hls = hls_New(*hls_stream, id, bw, psz_uri ? psz_uri : uri);
Brian Kurle's avatar
Brian Kurle committed
665
666

    free(psz_uri);
667
668

    return (*hls == NULL) ? VLC_ENOMEM : VLC_SUCCESS;
669
670
}

671
static int parse_MediaSequence(stream_t *s, hls_stream_t *hls, char *p_read)
672
{
673
674
    assert(hls);

675
676
677
678
679
    int sequence;
    int ret = sscanf(p_read, "#EXT-X-MEDIA-SEQUENCE:%d", &sequence);
    if (ret != 1)
    {
        msg_Err(s, "expected #EXT-X-MEDIA-SEQUENCE:<s>");
680
        return VLC_EGENERIC;
681
682
683
    }

    if (hls->sequence > 0)
684
685
686
687
688
689
690
691
692
693
694
695
    {
        if (s->p_sys->b_live)
        {
            hls_stream_t *last = hls_GetLast(s->p_sys->hls_stream);
            if ((last->sequence < sequence) && (sequence - last->sequence != 1))
                msg_Err(s, "EXT-X-MEDIA-SEQUENCE gap in playlist (new=%d, old=%d)",
                            sequence, last->sequence);
        }
        else
            msg_Err(s, "EXT-X-MEDIA-SEQUENCE already present in playlist (new=%d, old=%d)",
                        sequence, hls->sequence);
    }
696
    hls->sequence = sequence;
697
    return VLC_SUCCESS;
698
699
}

700
static int parse_Key(stream_t *s, hls_stream_t *hls, char *p_read)
701
{
702
703
    assert(hls);

704
    /* #EXT-X-KEY:METHOD=<method>[,URI="<URI>"][,IV=<IV>] */
705
706
    int err = VLC_SUCCESS;
    char *attr = parse_Attributes(p_read, "METHOD");
707
708
709
    if (attr == NULL)
    {
        msg_Err(s, "#EXT-X-KEY: expected METHOD=<value>");
710
        return err;
711
    }
712
713

    if (strncasecmp(attr, "NONE", 4) == 0)
714
    {
715

716
        char *uri = parse_Attributes(p_read, "URI");
717
        if (uri != NULL)
718
        {
719
            msg_Err(s, "#EXT-X-KEY: URI not expected");
720
            err = VLC_EGENERIC;
721
722
723
724
725
726
727
728
729
        }
        free(uri);
        /* IV is only supported in version 2 and above */
        if (hls->version >= 2)
        {
            char *iv = parse_Attributes(p_read, "IV");
            if (iv != NULL)
            {
                msg_Err(s, "#EXT-X-KEY: IV not expected");
730
                err = VLC_EGENERIC;
731
            }
732
733
734
735
736
737
            free(iv);
        }
    }
    else
    {
        msg_Warn(s, "playback of encrypted HTTP Live media is not supported.");
738
        err = VLC_EGENERIC;
739
740
    }
    free(attr);
741
    return err;
742
743
}

744
static int parse_ProgramDateTime(stream_t *s, hls_stream_t *hls, char *p_read)
745
{
746
    VLC_UNUSED(hls);
747
    msg_Dbg(s, "tag not supported: #EXT-X-PROGRAM-DATE-TIME %s", p_read);
748
    return VLC_SUCCESS;
749
750
}

751
static int parse_AllowCache(stream_t *s, hls_stream_t *hls, char *p_read)
752
{
753
754
    assert(hls);

755
756
757
758
759
    char answer[4] = "\0";
    int ret = sscanf(p_read, "#EXT-X-ALLOW-CACHE:%3s", answer);
    if (ret != 1)
    {
        msg_Err(s, "#EXT-X-ALLOW-CACHE, ignoring ...");
760
        return VLC_EGENERIC;
761
762
763
    }

    hls->b_cache = (strncmp(answer, "NO", 2) != 0);
764
    return VLC_SUCCESS;
765
766
}

767
static int parse_Version(stream_t *s, hls_stream_t *hls, char *p_read)
768
{
769
770
    assert(hls);

771
772
773
774
775
    int version;
    int ret = sscanf(p_read, "#EXT-X-VERSION:%d", &version);
    if (ret != 1)
    {
        msg_Err(s, "#EXT-X-VERSION: no protocol version found, should be version 1.");
776
        return VLC_EGENERIC;
777
778
779
780
781
782
783
    }

    /* Check version */
    hls->version = version;
    if (hls->version != 1)
    {
        msg_Err(s, "#EXT-X-VERSION should be version 1 iso %d", version);
784
        return VLC_EGENERIC;
785
    }
786
    return VLC_SUCCESS;
787
788
}

789
static int parse_EndList(stream_t *s, hls_stream_t *hls)
790
{
791
792
    assert(hls);

793
    s->p_sys->b_live = false;
794
    msg_Info(s, "video on demand (vod) mode");
795
    return VLC_SUCCESS;
796
797
}

798
static int parse_Discontinuity(stream_t *s, hls_stream_t *hls, char *p_read)
799
{
800
801
    assert(hls);

802
803
    /* FIXME: Do we need to act on discontinuity ?? */
    msg_Dbg(s, "#EXT-X-DISCONTINUITY %s", p_read);
804
    return VLC_SUCCESS;
805
806
}

807
808
809
810
811
812
/* The http://tools.ietf.org/html/draft-pantos-http-live-streaming-04#page-8
 * document defines the following new tags: EXT-X-TARGETDURATION,
 * EXT-X-MEDIA-SEQUENCE, EXT-X-KEY, EXT-X-PROGRAM-DATE-TIME, EXT-X-
 * ALLOW-CACHE, EXT-X-STREAM-INF, EXT-X-ENDLIST, EXT-X-DISCONTINUITY,
 * and EXT-X-VERSION.
 */
813
static int parse_M3U8(stream_t *s, vlc_array_t *streams, uint8_t *buffer, const ssize_t len)
814
815
{
    stream_sys_t *p_sys = s->p_sys;
816
    uint8_t *p_read, *p_begin, *p_end;
817

818
819
    assert(streams);
    assert(buffer);
820

821
822
823
824
825
826
    msg_Dbg(s, "parse_M3U8\n%s", buffer);
    p_begin = buffer;
    p_end = p_begin + len;

    char *line = ReadLine(p_begin, &p_read, p_end - p_begin);
    if (line == NULL)
827
        return VLC_ENOMEM;
828
    p_begin = p_read;
829
830
831

    if (strncmp(line, "#EXTM3U", 7) != 0)
    {
832
833
834
        msg_Err(s, "missing #EXTM3U tag .. aborting");
        free(line);
        return VLC_EGENERIC;
835
    }
836

837
838
839
    free(line);
    line = NULL;

840
841
842
843
    /* What is the version ? */
    int version = 1;
    uint8_t *p = (uint8_t *)strstr((const char *)buffer, "#EXT-X-VERSION:");
    if (p != NULL)
844
    {
845
846
847
848
849
850
        uint8_t *tmp = NULL;
        char *psz_version = ReadLine(p, &tmp, p_end - p);
        if (psz_version == NULL)
            return VLC_ENOMEM;
        int ret = sscanf((const char*)psz_version, "#EXT-X-VERSION:%d", &version);
        if (ret != 1)
851
        {
852
853
            msg_Warn(s, "#EXT-X-VERSION: no protocol version found, assuming version 1.");
            version = 1;
854
        }
855
856
        free(psz_version);
        p = NULL;
857
858
    }

859
860
    /* Is it a live stream ? */
    p_sys->b_live = (strstr((const char *)buffer, "#EXT-X-ENDLIST") == NULL) ? true : false;
861

862
863
    /* Is it a meta index file ? */
    bool b_meta = (strstr((const char *)buffer, "#EXT-X-STREAM-INF") == NULL) ? false : true;
864

865
    int err = VLC_SUCCESS;
866

867
868
869
    if (b_meta)
    {
        msg_Info(s, "Meta playlist");
870

871
        /* M3U8 Meta Index file */
872
873
874
875
876
877
878
879
        do {
            /* Next line */
            line = ReadLine(p_begin, &p_read, p_end - p_begin);
            if (line == NULL)
                break;
            p_begin = p_read;

            /* */
880
            if (strncmp(line, "#EXT-X-STREAM-INF", 17) == 0)
881
            {
882
883
884
885
                p_sys->b_meta = true;
                char *uri = ReadLine(p_begin, &p_read, p_end - p_begin);
                if (uri == NULL)
                    err = VLC_ENOMEM;
886
                else
887
888
889
890
891
892
893
                {
                    hls_stream_t *hls = NULL;
                    err = parse_StreamInformation(s, &streams, &hls, line, uri);
                    free(uri);

                    /* Download playlist file from server */
                    uint8_t *buf = NULL;
894
                    ssize_t len = read_M3U8_from_url(s, &hls->url, &buf);
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
                    if (len < 0)
                        err = VLC_EGENERIC;
                    else
                    {
                        /* Parse HLS m3u8 content. */
                        err = parse_M3U8(s, streams, buf, len);
                        free(buf);
                    }

                    if (hls)
                    {
                        hls->version = version;
                        if (!p_sys->b_live)
                            hls->size = hls_GetStreamSize(hls); /* Stream size (approximate) */
                    }
                }
911
                p_begin = p_read;
912
            }
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929

            free(line);
            line = NULL;

            if (p_begin >= p_end)
                break;

        } while ((err == VLC_SUCCESS) && vlc_object_alive(s));

    }
    else
    {
        msg_Info(s, "%s Playlist HLS protocol version: %d", p_sys->b_live ? "Live": "VOD", version);

        hls_stream_t *hls = NULL;
        if (p_sys->b_meta)
            hls = hls_GetLast(streams);
930
931
        else
        {
932
933
934
            /* No Meta playlist used */
            hls = hls_New(streams, 0, -1, NULL);
            if (hls)
935
            {
936
937
938
939
940
941
                /* Get TARGET-DURATION first */
                p = (uint8_t *)strstr((const char *)buffer, "#EXT-X-TARGETDURATION:");
                if (p)
                {
                    uint8_t *p_rest = NULL;
                    char *psz_duration = ReadLine(p, &p_rest,  p_end - p);
942
943
944
945
946
                    if (psz_duration == NULL)
                        return VLC_EGENERIC;
                    err = parse_TargetDuration(s, hls, psz_duration);
                    free(psz_duration);
                    p = NULL;
947
                }
948

949
                /* Store version */
950
                hls->version = version;
951
            }
952
953
954
955
956
            else return VLC_ENOMEM;
        }
        assert(hls);

        /* */
957
        int segment_duration = -1;
958
959
960
961
962
963
964
        do
        {
            /* Next line */
            line = ReadLine(p_begin, &p_read, p_end - p_begin);
            if (line == NULL)
                break;
            p_begin = p_read;
965

966
            if (strncmp(line, "#EXTINF", 7) == 0)
967
                err = parse_SegmentInformation(hls, line, &segment_duration);
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
            else if (strncmp(line, "#EXT-X-TARGETDURATION", 21) == 0)
                err = parse_TargetDuration(s, hls, line);
            else if (strncmp(line, "#EXT-X-MEDIA-SEQUENCE", 21) == 0)
                err = parse_MediaSequence(s, hls, line);
            else if (strncmp(line, "#EXT-X-KEY", 10) == 0)
                err = parse_Key(s, hls, line);
            else if (strncmp(line, "#EXT-X-PROGRAM-DATE-TIME", 24) == 0)
                err = parse_ProgramDateTime(s, hls, line);
            else if (strncmp(line, "#EXT-X-ALLOW-CACHE", 18) == 0)
                err = parse_AllowCache(s, hls, line);
            else if (strncmp(line, "#EXT-X-DISCONTINUITY", 20) == 0)
                err = parse_Discontinuity(s, hls, line);
            else if (strncmp(line, "#EXT-X-VERSION", 14) == 0)
                err = parse_Version(s, hls, line);
            else if (strncmp(line, "#EXT-X-ENDLIST", 14) == 0)
                err = parse_EndList(s, hls);
984
            else if ((strncmp(line, "#", 1) != 0) && (*line != '\0') )
985
986
987
988
            {
                err = parse_AddSegment(s, hls, segment_duration, line);
                segment_duration = -1; /* reset duration */
            }
989

990
991
            free(line);
            line = NULL;
992

993
994
            if (p_begin >= p_end)
                break;
995

996
        } while ((err == VLC_SUCCESS) && vlc_object_alive(s));
997

998
999
        free(line);
    }
1000