1/*
2 * Apple HTTP Live Streaming demuxer
3 * Copyright (c) 2010 Martin Storsjo
4 *
5 * This file is part of Libav.
6 *
7 * Libav is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * Libav is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with Libav; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22/**
23 * @file
24 * Apple HTTP Live Streaming demuxer
25 * http://tools.ietf.org/html/draft-pantos-http-live-streaming
26 */
27
28#include "libavutil/avstring.h"
29#include "libavutil/intreadwrite.h"
30#include "libavutil/mathematics.h"
31#include "libavutil/opt.h"
32#include "libavutil/dict.h"
33#include "avformat.h"
34#include "internal.h"
35#include <unistd.h>
36#include "avio_internal.h"
37#include "url.h"
38
39#define INITIAL_BUFFER_SIZE 32768
40
41/*
42 * An apple http stream consists of a playlist with media segment files,
43 * played sequentially. There may be several playlists with the same
44 * video content, in different bandwidth variants, that are played in
45 * parallel (preferrably only one bandwidth variant at a time). In this case,
46 * the user supplied the url to a main playlist that only lists the variant
47 * playlists.
48 *
49 * If the main playlist doesn't point at any variants, we still create
50 * one anonymous toplevel variant for this, to maintain the structure.
51 */
52
53enum KeyType {
54    KEY_NONE,
55    KEY_AES_128,
56};
57
58struct segment {
59    int duration;
60    char url[MAX_URL_SIZE];
61    char key[MAX_URL_SIZE];
62    enum KeyType key_type;
63    uint8_t iv[16];
64};
65
66/*
67 * Each variant has its own demuxer. If it currently is active,
68 * it has an open AVIOContext too, and potentially an AVPacket
69 * containing the next packet from this stream.
70 */
71struct variant {
72    int bandwidth;
73    char url[MAX_URL_SIZE];
74    AVIOContext pb;
75    uint8_t* read_buffer;
76    URLContext *input;
77    AVFormatContext *parent;
78    int index;
79    AVFormatContext *ctx;
80    AVPacket pkt;
81    int stream_offset;
82
83    int finished;
84    int target_duration;
85    int start_seq_no;
86    int n_segments;
87    struct segment **segments;
88    int needed, cur_needed;
89    int cur_seq_no;
90    int64_t last_load_time;
91
92    char key_url[MAX_URL_SIZE];
93    uint8_t key[16];
94};
95
96typedef struct AppleHTTPContext {
97    int n_variants;
98    struct variant **variants;
99    int cur_seq_no;
100    int end_of_segment;
101    int first_packet;
102    int64_t first_timestamp;
103    AVIOInterruptCB *interrupt_callback;
104} AppleHTTPContext;
105
106static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
107{
108    int len = ff_get_line(s, buf, maxlen);
109    while (len > 0 && isspace(buf[len - 1]))
110        buf[--len] = '\0';
111    return len;
112}
113
114static void free_segment_list(struct variant *var)
115{
116    int i;
117    for (i = 0; i < var->n_segments; i++)
118        av_free(var->segments[i]);
119    av_freep(&var->segments);
120    var->n_segments = 0;
121}
122
123static void free_variant_list(AppleHTTPContext *c)
124{
125    int i;
126    for (i = 0; i < c->n_variants; i++) {
127        struct variant *var = c->variants[i];
128        free_segment_list(var);
129        av_free_packet(&var->pkt);
130        av_free(var->pb.buffer);
131        if (var->input)
132            ffurl_close(var->input);
133        if (var->ctx) {
134            var->ctx->pb = NULL;
135            avformat_close_input(&var->ctx);
136        }
137        av_free(var);
138    }
139    av_freep(&c->variants);
140    c->n_variants = 0;
141}
142
143/*
144 * Used to reset a statically allocated AVPacket to a clean slate,
145 * containing no data.
146 */
147static void reset_packet(AVPacket *pkt)
148{
149    av_init_packet(pkt);
150    pkt->data = NULL;
151}
152
153static struct variant *new_variant(AppleHTTPContext *c, int bandwidth,
154                                   const char *url, const char *base)
155{
156    struct variant *var = av_mallocz(sizeof(struct variant));
157    if (!var)
158        return NULL;
159    reset_packet(&var->pkt);
160    var->bandwidth = bandwidth;
161    ff_make_absolute_url(var->url, sizeof(var->url), base, url);
162    dynarray_add(&c->variants, &c->n_variants, var);
163    return var;
164}
165
166struct variant_info {
167    char bandwidth[20];
168};
169
170static void handle_variant_args(struct variant_info *info, const char *key,
171                                int key_len, char **dest, int *dest_len)
172{
173    if (!strncmp(key, "BANDWIDTH=", key_len)) {
174        *dest     =        info->bandwidth;
175        *dest_len = sizeof(info->bandwidth);
176    }
177}
178
179struct key_info {
180     char uri[MAX_URL_SIZE];
181     char method[10];
182     char iv[35];
183};
184
185static void handle_key_args(struct key_info *info, const char *key,
186                            int key_len, char **dest, int *dest_len)
187{
188    if (!strncmp(key, "METHOD=", key_len)) {
189        *dest     =        info->method;
190        *dest_len = sizeof(info->method);
191    } else if (!strncmp(key, "URI=", key_len)) {
192        *dest     =        info->uri;
193        *dest_len = sizeof(info->uri);
194    } else if (!strncmp(key, "IV=", key_len)) {
195        *dest     =        info->iv;
196        *dest_len = sizeof(info->iv);
197    }
198}
199
200static int parse_playlist(AppleHTTPContext *c, const char *url,
201                          struct variant *var, AVIOContext *in)
202{
203    int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
204    enum KeyType key_type = KEY_NONE;
205    uint8_t iv[16] = "";
206    int has_iv = 0;
207    char key[MAX_URL_SIZE] = "";
208    char line[1024];
209    const char *ptr;
210    int close_in = 0;
211
212    if (!in) {
213        close_in = 1;
214        if ((ret = avio_open2(&in, url, AVIO_FLAG_READ,
215                              c->interrupt_callback, NULL)) < 0)
216            return ret;
217    }
218
219    read_chomp_line(in, line, sizeof(line));
220    if (strcmp(line, "#EXTM3U")) {
221        ret = AVERROR_INVALIDDATA;
222        goto fail;
223    }
224
225    if (var) {
226        free_segment_list(var);
227        var->finished = 0;
228    }
229    while (!in->eof_reached) {
230        read_chomp_line(in, line, sizeof(line));
231        if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
232            struct variant_info info = {{0}};
233            is_variant = 1;
234            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args,
235                               &info);
236            bandwidth = atoi(info.bandwidth);
237        } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) {
238            struct key_info info = {{0}};
239            ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args,
240                               &info);
241            key_type = KEY_NONE;
242            has_iv = 0;
243            if (!strcmp(info.method, "AES-128"))
244                key_type = KEY_AES_128;
245            if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) {
246                ff_hex_to_data(iv, info.iv + 2);
247                has_iv = 1;
248            }
249            av_strlcpy(key, info.uri, sizeof(key));
250        } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
251            if (!var) {
252                var = new_variant(c, 0, url, NULL);
253                if (!var) {
254                    ret = AVERROR(ENOMEM);
255                    goto fail;
256                }
257            }
258            var->target_duration = atoi(ptr);
259        } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
260            if (!var) {
261                var = new_variant(c, 0, url, NULL);
262                if (!var) {
263                    ret = AVERROR(ENOMEM);
264                    goto fail;
265                }
266            }
267            var->start_seq_no = atoi(ptr);
268        } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
269            if (var)
270                var->finished = 1;
271        } else if (av_strstart(line, "#EXTINF:", &ptr)) {
272            is_segment = 1;
273            duration   = atoi(ptr);
274        } else if (av_strstart(line, "#", NULL)) {
275            continue;
276        } else if (line[0]) {
277            if (is_variant) {
278                if (!new_variant(c, bandwidth, line, url)) {
279                    ret = AVERROR(ENOMEM);
280                    goto fail;
281                }
282                is_variant = 0;
283                bandwidth  = 0;
284            }
285            if (is_segment) {
286                struct segment *seg;
287                if (!var) {
288                    var = new_variant(c, 0, url, NULL);
289                    if (!var) {
290                        ret = AVERROR(ENOMEM);
291                        goto fail;
292                    }
293                }
294                seg = av_malloc(sizeof(struct segment));
295                if (!seg) {
296                    ret = AVERROR(ENOMEM);
297                    goto fail;
298                }
299                seg->duration = duration;
300                seg->key_type = key_type;
301                if (has_iv) {
302                    memcpy(seg->iv, iv, sizeof(iv));
303                } else {
304                    int seq = var->start_seq_no + var->n_segments;
305                    memset(seg->iv, 0, sizeof(seg->iv));
306                    AV_WB32(seg->iv + 12, seq);
307                }
308                ff_make_absolute_url(seg->key, sizeof(seg->key), url, key);
309                ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
310                dynarray_add(&var->segments, &var->n_segments, seg);
311                is_segment = 0;
312            }
313        }
314    }
315    if (var)
316        var->last_load_time = av_gettime();
317
318fail:
319    if (close_in)
320        avio_close(in);
321    return ret;
322}
323
324static int open_input(struct variant *var)
325{
326    struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no];
327    if (seg->key_type == KEY_NONE) {
328        return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ,
329                          &var->parent->interrupt_callback, NULL);
330    } else if (seg->key_type == KEY_AES_128) {
331        char iv[33], key[33], url[MAX_URL_SIZE];
332        int ret;
333        if (strcmp(seg->key, var->key_url)) {
334            URLContext *uc;
335            if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ,
336                           &var->parent->interrupt_callback, NULL) == 0) {
337                if (ffurl_read_complete(uc, var->key, sizeof(var->key))
338                    != sizeof(var->key)) {
339                    av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n",
340                           seg->key);
341                }
342                ffurl_close(uc);
343            } else {
344                av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n",
345                       seg->key);
346            }
347            av_strlcpy(var->key_url, seg->key, sizeof(var->key_url));
348        }
349        ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0);
350        ff_data_to_hex(key, var->key, sizeof(var->key), 0);
351        iv[32] = key[32] = '\0';
352        if (strstr(seg->url, "://"))
353            snprintf(url, sizeof(url), "crypto+%s", seg->url);
354        else
355            snprintf(url, sizeof(url), "crypto:%s", seg->url);
356        if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ,
357                               &var->parent->interrupt_callback)) < 0)
358            return ret;
359        av_opt_set(var->input->priv_data, "key", key, 0);
360        av_opt_set(var->input->priv_data, "iv", iv, 0);
361        if ((ret = ffurl_connect(var->input, NULL)) < 0) {
362            ffurl_close(var->input);
363            var->input = NULL;
364            return ret;
365        }
366        return 0;
367    }
368    return AVERROR(ENOSYS);
369}
370
371static int read_data(void *opaque, uint8_t *buf, int buf_size)
372{
373    struct variant *v = opaque;
374    AppleHTTPContext *c = v->parent->priv_data;
375    int ret, i;
376
377restart:
378    if (!v->input) {
379        /* If this is a live stream and the reload interval has elapsed since
380         * the last playlist reload, reload the variant playlists now. */
381        int64_t reload_interval = v->n_segments > 0 ?
382                                  v->segments[v->n_segments - 1]->duration :
383                                  v->target_duration;
384        reload_interval *= 1000000;
385
386reload:
387        if (!v->finished &&
388            av_gettime() - v->last_load_time >= reload_interval) {
389            if ((ret = parse_playlist(c, v->url, v, NULL)) < 0)
390                return ret;
391            /* If we need to reload the playlist again below (if
392             * there's still no more segments), switch to a reload
393             * interval of half the target duration. */
394            reload_interval = v->target_duration * 500000;
395        }
396        if (v->cur_seq_no < v->start_seq_no) {
397            av_log(NULL, AV_LOG_WARNING,
398                   "skipping %d segments ahead, expired from playlists\n",
399                   v->start_seq_no - v->cur_seq_no);
400            v->cur_seq_no = v->start_seq_no;
401        }
402        if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
403            if (v->finished)
404                return AVERROR_EOF;
405            while (av_gettime() - v->last_load_time < reload_interval) {
406                if (ff_check_interrupt(c->interrupt_callback))
407                    return AVERROR_EXIT;
408                usleep(100*1000);
409            }
410            /* Enough time has elapsed since the last reload */
411            goto reload;
412        }
413
414        ret = open_input(v);
415        if (ret < 0)
416            return ret;
417    }
418    ret = ffurl_read(v->input, buf, buf_size);
419    if (ret > 0)
420        return ret;
421    if (ret < 0 && ret != AVERROR_EOF)
422        return ret;
423    ffurl_close(v->input);
424    v->input = NULL;
425    v->cur_seq_no++;
426
427    c->end_of_segment = 1;
428    c->cur_seq_no = v->cur_seq_no;
429
430    if (v->ctx && v->ctx->nb_streams) {
431        v->needed = 0;
432        for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams;
433             i++) {
434            if (v->parent->streams[i]->discard < AVDISCARD_ALL)
435                v->needed = 1;
436        }
437    }
438    if (!v->needed) {
439        av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n",
440               v->index);
441        return AVERROR_EOF;
442    }
443    goto restart;
444}
445
446static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap)
447{
448    AppleHTTPContext *c = s->priv_data;
449    int ret = 0, i, j, stream_offset = 0;
450
451    c->interrupt_callback = &s->interrupt_callback;
452
453    if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0)
454        goto fail;
455
456    if (c->n_variants == 0) {
457        av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
458        ret = AVERROR_EOF;
459        goto fail;
460    }
461    /* If the playlist only contained variants, parse each individual
462     * variant playlist. */
463    if (c->n_variants > 1 || c->variants[0]->n_segments == 0) {
464        for (i = 0; i < c->n_variants; i++) {
465            struct variant *v = c->variants[i];
466            if ((ret = parse_playlist(c, v->url, v, NULL)) < 0)
467                goto fail;
468        }
469    }
470
471    if (c->variants[0]->n_segments == 0) {
472        av_log(NULL, AV_LOG_WARNING, "Empty playlist\n");
473        ret = AVERROR_EOF;
474        goto fail;
475    }
476
477    /* If this isn't a live stream, calculate the total duration of the
478     * stream. */
479    if (c->variants[0]->finished) {
480        int64_t duration = 0;
481        for (i = 0; i < c->variants[0]->n_segments; i++)
482            duration += c->variants[0]->segments[i]->duration;
483        s->duration = duration * AV_TIME_BASE;
484    }
485
486    /* Open the demuxer for each variant */
487    for (i = 0; i < c->n_variants; i++) {
488        struct variant *v = c->variants[i];
489        AVInputFormat *in_fmt = NULL;
490        char bitrate_str[20];
491        if (v->n_segments == 0)
492            continue;
493
494        if (!(v->ctx = avformat_alloc_context())) {
495            ret = AVERROR(ENOMEM);
496            goto fail;
497        }
498
499        v->index  = i;
500        v->needed = 1;
501        v->parent = s;
502
503        /* If this is a live stream with more than 3 segments, start at the
504         * third last segment. */
505        v->cur_seq_no = v->start_seq_no;
506        if (!v->finished && v->n_segments > 3)
507            v->cur_seq_no = v->start_seq_no + v->n_segments - 3;
508
509        v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
510        ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v,
511                          read_data, NULL, NULL);
512        v->pb.seekable = 0;
513        ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url,
514                                    NULL, 0, 0);
515        if (ret < 0) {
516            /* Free the ctx - it isn't initialized properly at this point,
517             * so avformat_close_input shouldn't be called. If
518             * avformat_open_input fails below, it frees and zeros the
519             * context, so it doesn't need any special treatment like this. */
520            avformat_free_context(v->ctx);
521            v->ctx = NULL;
522            goto fail;
523        }
524        v->ctx->pb       = &v->pb;
525        ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL);
526        if (ret < 0)
527            goto fail;
528        v->stream_offset = stream_offset;
529        snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth);
530        /* Create new AVStreams for each stream in this variant */
531        for (j = 0; j < v->ctx->nb_streams; j++) {
532            AVStream *st = avformat_new_stream(s, NULL);
533            if (!st) {
534                ret = AVERROR(ENOMEM);
535                goto fail;
536            }
537            st->id = i;
538            avcodec_copy_context(st->codec, v->ctx->streams[j]->codec);
539            if (v->bandwidth)
540                av_dict_set(&st->metadata, "variant_bitrate", bitrate_str,
541                                 0);
542        }
543        stream_offset += v->ctx->nb_streams;
544    }
545
546    c->first_packet = 1;
547    c->first_timestamp = AV_NOPTS_VALUE;
548
549    return 0;
550fail:
551    free_variant_list(c);
552    return ret;
553}
554
555static int recheck_discard_flags(AVFormatContext *s, int first)
556{
557    AppleHTTPContext *c = s->priv_data;
558    int i, changed = 0;
559
560    /* Check if any new streams are needed */
561    for (i = 0; i < c->n_variants; i++)
562        c->variants[i]->cur_needed = 0;;
563
564    for (i = 0; i < s->nb_streams; i++) {
565        AVStream *st = s->streams[i];
566        struct variant *var = c->variants[s->streams[i]->id];
567        if (st->discard < AVDISCARD_ALL)
568            var->cur_needed = 1;
569    }
570    for (i = 0; i < c->n_variants; i++) {
571        struct variant *v = c->variants[i];
572        if (v->cur_needed && !v->needed) {
573            v->needed = 1;
574            changed = 1;
575            v->cur_seq_no = c->cur_seq_no;
576            v->pb.eof_reached = 0;
577            av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i);
578        } else if (first && !v->cur_needed && v->needed) {
579            if (v->input)
580                ffurl_close(v->input);
581            v->input = NULL;
582            v->needed = 0;
583            changed = 1;
584            av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i);
585        }
586    }
587    return changed;
588}
589
590static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt)
591{
592    AppleHTTPContext *c = s->priv_data;
593    int ret, i, minvariant = -1;
594
595    if (c->first_packet) {
596        recheck_discard_flags(s, 1);
597        c->first_packet = 0;
598    }
599
600start:
601    c->end_of_segment = 0;
602    for (i = 0; i < c->n_variants; i++) {
603        struct variant *var = c->variants[i];
604        /* Make sure we've got one buffered packet from each open variant
605         * stream */
606        if (var->needed && !var->pkt.data) {
607            ret = av_read_frame(var->ctx, &var->pkt);
608            if (ret < 0) {
609                if (!var->pb.eof_reached)
610                    return ret;
611                reset_packet(&var->pkt);
612            } else {
613                if (c->first_timestamp == AV_NOPTS_VALUE)
614                    c->first_timestamp = var->pkt.dts;
615            }
616        }
617        /* Check if this stream has the packet with the lowest dts */
618        if (var->pkt.data) {
619            if (minvariant < 0 ||
620                var->pkt.dts < c->variants[minvariant]->pkt.dts)
621                minvariant = i;
622        }
623    }
624    if (c->end_of_segment) {
625        if (recheck_discard_flags(s, 0))
626            goto start;
627    }
628    /* If we got a packet, return it */
629    if (minvariant >= 0) {
630        *pkt = c->variants[minvariant]->pkt;
631        pkt->stream_index += c->variants[minvariant]->stream_offset;
632        reset_packet(&c->variants[minvariant]->pkt);
633        return 0;
634    }
635    return AVERROR_EOF;
636}
637
638static int applehttp_close(AVFormatContext *s)
639{
640    AppleHTTPContext *c = s->priv_data;
641
642    free_variant_list(c);
643    return 0;
644}
645
646static int applehttp_read_seek(AVFormatContext *s, int stream_index,
647                               int64_t timestamp, int flags)
648{
649    AppleHTTPContext *c = s->priv_data;
650    int i, j, ret;
651
652    if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished)
653        return AVERROR(ENOSYS);
654
655    timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ?
656                               s->streams[stream_index]->time_base.den :
657                               AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ?
658                               AV_ROUND_DOWN : AV_ROUND_UP);
659    ret = AVERROR(EIO);
660    for (i = 0; i < c->n_variants; i++) {
661        /* Reset reading */
662        struct variant *var = c->variants[i];
663        int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 :
664                      av_rescale_rnd(c->first_timestamp, 1,
665                          stream_index >= 0 ? s->streams[stream_index]->time_base.den : AV_TIME_BASE,
666                          flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP);
667        if (var->input) {
668            ffurl_close(var->input);
669            var->input = NULL;
670        }
671        av_free_packet(&var->pkt);
672        reset_packet(&var->pkt);
673        var->pb.eof_reached = 0;
674
675        /* Locate the segment that contains the target timestamp */
676        for (j = 0; j < var->n_segments; j++) {
677            if (timestamp >= pos &&
678                timestamp < pos + var->segments[j]->duration) {
679                var->cur_seq_no = var->start_seq_no + j;
680                ret = 0;
681                break;
682            }
683            pos += var->segments[j]->duration;
684        }
685    }
686    return ret;
687}
688
689static int applehttp_probe(AVProbeData *p)
690{
691    /* Require #EXTM3U at the start, and either one of the ones below
692     * somewhere for a proper match. */
693    if (strncmp(p->buf, "#EXTM3U", 7))
694        return 0;
695    if (strstr(p->buf, "#EXT-X-STREAM-INF:")     ||
696        strstr(p->buf, "#EXT-X-TARGETDURATION:") ||
697        strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:"))
698        return AVPROBE_SCORE_MAX;
699    return 0;
700}
701
702AVInputFormat ff_applehttp_demuxer = {
703    .name           = "applehttp",
704    .long_name      = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"),
705    .priv_data_size = sizeof(AppleHTTPContext),
706    .read_probe     = applehttp_probe,
707    .read_header    = applehttp_read_header,
708    .read_packet    = applehttp_read_packet,
709    .read_close     = applehttp_close,
710    .read_seek      = applehttp_read_seek,
711};
712