2 * Apple HTTP Live Streaming Protocol Handler
3 * Copyright (c) 2010 Martin Storsjo
5 * This file is part of Libav.
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.
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.
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
24 * Apple HTTP Live Streaming Protocol Handler
25 * http://tools.ietf.org/html/draft-pantos-http-live-streaming
28 #include "libavutil/avstring.h"
29 #include "libavutil/time.h"
36 * An apple http stream consists of a playlist with media segment files,
37 * played sequentially. There may be several playlists with the same
38 * video content, in different bandwidth variants, that are played in
39 * parallel (preferably only one bandwidth variant at a time). In this case,
40 * the user supplied the url to a main playlist that only lists the variant
43 * If the main playlist doesn't point at any variants, we still create
44 * one anonymous toplevel variant for this, to maintain the structure.
49 char url
[MAX_URL_SIZE
];
54 char url
[MAX_URL_SIZE
];
57 typedef struct HLSContext
{
58 char playlisturl
[MAX_URL_SIZE
];
63 struct segment
**segments
;
65 struct variant
**variants
;
68 int64_t last_load_time
;
71 static int read_chomp_line(AVIOContext
*s
, char *buf
, int maxlen
)
73 int len
= ff_get_line(s
, buf
, maxlen
);
74 while (len
> 0 && av_isspace(buf
[len
- 1]))
79 static void free_segment_list(HLSContext
*s
)
82 for (i
= 0; i
< s
->n_segments
; i
++)
83 av_free(s
->segments
[i
]);
84 av_freep(&s
->segments
);
88 static void free_variant_list(HLSContext
*s
)
91 for (i
= 0; i
< s
->n_variants
; i
++)
92 av_free(s
->variants
[i
]);
93 av_freep(&s
->variants
);
101 static void handle_variant_args(struct variant_info
*info
, const char *key
,
102 int key_len
, char **dest
, int *dest_len
)
104 if (!strncmp(key
, "BANDWIDTH=", key_len
)) {
105 *dest
= info
->bandwidth
;
106 *dest_len
= sizeof(info
->bandwidth
);
110 static int parse_playlist(URLContext
*h
, const char *url
)
112 HLSContext
*s
= h
->priv_data
;
114 int ret
= 0, duration
= 0, is_segment
= 0, is_variant
= 0, bandwidth
= 0;
118 if ((ret
= avio_open2(&in
, url
, AVIO_FLAG_READ
,
119 &h
->interrupt_callback
, NULL
)) < 0)
122 read_chomp_line(in
, line
, sizeof(line
));
123 if (strcmp(line
, "#EXTM3U"))
124 return AVERROR_INVALIDDATA
;
126 free_segment_list(s
);
128 while (!in
->eof_reached
) {
129 read_chomp_line(in
, line
, sizeof(line
));
130 if (av_strstart(line
, "#EXT-X-STREAM-INF:", &ptr
)) {
131 struct variant_info info
= {{0}};
133 ff_parse_key_value(ptr
, (ff_parse_key_val_cb
) handle_variant_args
,
135 bandwidth
= atoi(info
.bandwidth
);
136 } else if (av_strstart(line
, "#EXT-X-TARGETDURATION:", &ptr
)) {
137 s
->target_duration
= atoi(ptr
);
138 } else if (av_strstart(line
, "#EXT-X-MEDIA-SEQUENCE:", &ptr
)) {
139 s
->start_seq_no
= atoi(ptr
);
140 } else if (av_strstart(line
, "#EXT-X-ENDLIST", &ptr
)) {
142 } else if (av_strstart(line
, "#EXTINF:", &ptr
)) {
144 duration
= atoi(ptr
);
145 } else if (av_strstart(line
, "#", NULL
)) {
147 } else if (line
[0]) {
149 struct segment
*seg
= av_malloc(sizeof(struct segment
));
151 ret
= AVERROR(ENOMEM
);
154 seg
->duration
= duration
;
155 ff_make_absolute_url(seg
->url
, sizeof(seg
->url
), url
, line
);
156 dynarray_add(&s
->segments
, &s
->n_segments
, seg
);
158 } else if (is_variant
) {
159 struct variant
*var
= av_malloc(sizeof(struct variant
));
161 ret
= AVERROR(ENOMEM
);
164 var
->bandwidth
= bandwidth
;
165 ff_make_absolute_url(var
->url
, sizeof(var
->url
), url
, line
);
166 dynarray_add(&s
->variants
, &s
->n_variants
, var
);
171 s
->last_load_time
= av_gettime();
178 static int hls_close(URLContext
*h
)
180 HLSContext
*s
= h
->priv_data
;
182 free_segment_list(s
);
183 free_variant_list(s
);
184 ffurl_close(s
->seg_hd
);
188 static int hls_open(URLContext
*h
, const char *uri
, int flags
)
190 HLSContext
*s
= h
->priv_data
;
192 const char *nested_url
;
194 if (flags
& AVIO_FLAG_WRITE
)
195 return AVERROR(ENOSYS
);
199 if (av_strstart(uri
, "hls+", &nested_url
)) {
200 av_strlcpy(s
->playlisturl
, nested_url
, sizeof(s
->playlisturl
));
201 } else if (av_strstart(uri
, "hls://", &nested_url
)) {
202 av_log(h
, AV_LOG_ERROR
,
203 "No nested protocol specified. Specify e.g. hls+http://%s\n",
205 ret
= AVERROR(EINVAL
);
207 #if FF_API_APPLEHTTP_PROTO
208 } else if (av_strstart(uri
, "applehttp+", &nested_url
)) {
209 av_strlcpy(s
->playlisturl
, nested_url
, sizeof(s
->playlisturl
));
210 av_log(h
, AV_LOG_WARNING
,
211 "The applehttp protocol is deprecated, use hls+%s as url "
212 "instead.\n", nested_url
);
213 } else if (av_strstart(uri
, "applehttp://", &nested_url
)) {
214 av_strlcpy(s
->playlisturl
, "http://", sizeof(s
->playlisturl
));
215 av_strlcat(s
->playlisturl
, nested_url
, sizeof(s
->playlisturl
));
216 av_log(h
, AV_LOG_WARNING
,
217 "The applehttp protocol is deprecated, use hls+http://%s as url "
218 "instead.\n", nested_url
);
221 av_log(h
, AV_LOG_ERROR
, "Unsupported url %s\n", uri
);
222 ret
= AVERROR(EINVAL
);
225 av_log(h
, AV_LOG_WARNING
,
226 "Using the hls protocol is discouraged, please try using the "
227 "hls demuxer instead. The hls demuxer should be more complete "
228 "and work as well as the protocol implementation. (If not, "
229 "please report it.) To use the demuxer, simply use %s as url.\n",
232 if ((ret
= parse_playlist(h
, s
->playlisturl
)) < 0)
235 if (s
->n_segments
== 0 && s
->n_variants
> 0) {
236 int max_bandwidth
= 0, maxvar
= -1;
237 for (i
= 0; i
< s
->n_variants
; i
++) {
238 if (s
->variants
[i
]->bandwidth
> max_bandwidth
|| i
== 0) {
239 max_bandwidth
= s
->variants
[i
]->bandwidth
;
243 av_strlcpy(s
->playlisturl
, s
->variants
[maxvar
]->url
,
244 sizeof(s
->playlisturl
));
245 if ((ret
= parse_playlist(h
, s
->playlisturl
)) < 0)
249 if (s
->n_segments
== 0) {
250 av_log(h
, AV_LOG_WARNING
, "Empty playlist\n");
254 s
->cur_seq_no
= s
->start_seq_no
;
255 if (!s
->finished
&& s
->n_segments
>= 3)
256 s
->cur_seq_no
= s
->start_seq_no
+ s
->n_segments
- 3;
265 static int hls_read(URLContext
*h
, uint8_t *buf
, int size
)
267 HLSContext
*s
= h
->priv_data
;
270 int64_t reload_interval
;
274 ret
= ffurl_read(s
->seg_hd
, buf
, size
);
279 ffurl_close(s
->seg_hd
);
283 reload_interval
= s
->n_segments
> 0 ?
284 s
->segments
[s
->n_segments
- 1]->duration
:
286 reload_interval
*= 1000000;
289 int64_t now
= av_gettime();
290 if (now
- s
->last_load_time
>= reload_interval
) {
291 if ((ret
= parse_playlist(h
, s
->playlisturl
)) < 0)
293 /* If we need to reload the playlist again below (if
294 * there's still no more segments), switch to a reload
295 * interval of half the target duration. */
296 reload_interval
= s
->target_duration
* 500000;
299 if (s
->cur_seq_no
< s
->start_seq_no
) {
300 av_log(h
, AV_LOG_WARNING
,
301 "skipping %d segments ahead, expired from playlist\n",
302 s
->start_seq_no
- s
->cur_seq_no
);
303 s
->cur_seq_no
= s
->start_seq_no
;
305 if (s
->cur_seq_no
- s
->start_seq_no
>= s
->n_segments
) {
308 while (av_gettime() - s
->last_load_time
< reload_interval
) {
309 if (ff_check_interrupt(&h
->interrupt_callback
))
315 url
= s
->segments
[s
->cur_seq_no
- s
->start_seq_no
]->url
,
316 av_log(h
, AV_LOG_DEBUG
, "opening %s\n", url
);
317 ret
= ffurl_open(&s
->seg_hd
, url
, AVIO_FLAG_READ
,
318 &h
->interrupt_callback
, NULL
);
320 if (ff_check_interrupt(&h
->interrupt_callback
))
322 av_log(h
, AV_LOG_WARNING
, "Unable to open %s\n", url
);
329 #if FF_API_APPLEHTTP_PROTO
330 URLProtocol ff_applehttp_protocol
= {
332 .url_open
= hls_open
,
333 .url_read
= hls_read
,
334 .url_close
= hls_close
,
335 .flags
= URL_PROTOCOL_FLAG_NESTED_SCHEME
,
336 .priv_data_size
= sizeof(HLSContext
),
340 URLProtocol ff_hls_protocol
= {
342 .url_open
= hls_open
,
343 .url_read
= hls_read
,
344 .url_close
= hls_close
,
345 .flags
= URL_PROTOCOL_FLAG_NESTED_SCHEME
,
346 .priv_data_size
= sizeof(HLSContext
),