2 * Apple HTTP Live Streaming Protocol Handler
3 * Copyright (c) 2010 Martin Storsjo
5 * This file is part of FFmpeg.
7 * FFmpeg 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 * FFmpeg 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 FFmpeg; 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"
31 #include "avio_internal.h"
37 * An apple http stream consists of a playlist with media segment files,
38 * played sequentially. There may be several playlists with the same
39 * video content, in different bandwidth variants, that are played in
40 * parallel (preferably only one bandwidth variant at a time). In this case,
41 * the user supplied the url to a main playlist that only lists the variant
44 * If the main playlist doesn't point at any variants, we still create
45 * one anonymous toplevel variant for this, to maintain the structure.
50 char url
[MAX_URL_SIZE
];
55 char url
[MAX_URL_SIZE
];
58 typedef struct HLSContext
{
59 char playlisturl
[MAX_URL_SIZE
];
60 int64_t target_duration
;
64 struct segment
**segments
;
66 struct variant
**variants
;
69 int64_t last_load_time
;
72 static void free_segment_list(HLSContext
*s
)
75 for (i
= 0; i
< s
->n_segments
; i
++)
76 av_freep(&s
->segments
[i
]);
77 av_freep(&s
->segments
);
81 static void free_variant_list(HLSContext
*s
)
84 for (i
= 0; i
< s
->n_variants
; i
++)
85 av_freep(&s
->variants
[i
]);
86 av_freep(&s
->variants
);
94 static void handle_variant_args(struct variant_info
*info
, const char *key
,
95 int key_len
, char **dest
, int *dest_len
)
97 if (!strncmp(key
, "BANDWIDTH=", key_len
)) {
98 *dest
= info
->bandwidth
;
99 *dest_len
= sizeof(info
->bandwidth
);
103 static int parse_playlist(URLContext
*h
, const char *url
)
105 HLSContext
*s
= h
->priv_data
;
107 int ret
= 0, is_segment
= 0, is_variant
= 0, bandwidth
= 0;
108 int64_t duration
= 0;
112 if ((ret
= ffio_open_whitelist(&in
, url
, AVIO_FLAG_READ
,
113 &h
->interrupt_callback
, NULL
,
114 h
->protocol_whitelist
, h
->protocol_blacklist
)) < 0)
117 ff_get_chomp_line(in
, line
, sizeof(line
));
118 if (strcmp(line
, "#EXTM3U")) {
119 ret
= AVERROR_INVALIDDATA
;
123 free_segment_list(s
);
125 while (!avio_feof(in
)) {
126 ff_get_chomp_line(in
, line
, sizeof(line
));
127 if (av_strstart(line
, "#EXT-X-STREAM-INF:", &ptr
)) {
128 struct variant_info info
= {{0}};
130 ff_parse_key_value(ptr
, (ff_parse_key_val_cb
) handle_variant_args
,
132 bandwidth
= atoi(info
.bandwidth
);
133 } else if (av_strstart(line
, "#EXT-X-TARGETDURATION:", &ptr
)) {
134 s
->target_duration
= atoi(ptr
) * AV_TIME_BASE
;
135 } else if (av_strstart(line
, "#EXT-X-MEDIA-SEQUENCE:", &ptr
)) {
136 s
->start_seq_no
= atoi(ptr
);
137 } else if (av_strstart(line
, "#EXT-X-ENDLIST", &ptr
)) {
139 } else if (av_strstart(line
, "#EXTINF:", &ptr
)) {
141 duration
= atof(ptr
) * AV_TIME_BASE
;
142 } else if (av_strstart(line
, "#", NULL
)) {
144 } else if (line
[0]) {
146 struct segment
*seg
= av_malloc(sizeof(struct segment
));
148 ret
= AVERROR(ENOMEM
);
151 seg
->duration
= duration
;
152 ff_make_absolute_url(seg
->url
, sizeof(seg
->url
), url
, line
);
153 dynarray_add(&s
->segments
, &s
->n_segments
, seg
);
155 } else if (is_variant
) {
156 struct variant
*var
= av_malloc(sizeof(struct variant
));
158 ret
= AVERROR(ENOMEM
);
161 var
->bandwidth
= bandwidth
;
162 ff_make_absolute_url(var
->url
, sizeof(var
->url
), url
, line
);
163 dynarray_add(&s
->variants
, &s
->n_variants
, var
);
168 s
->last_load_time
= av_gettime_relative();
175 static int hls_close(URLContext
*h
)
177 HLSContext
*s
= h
->priv_data
;
179 free_segment_list(s
);
180 free_variant_list(s
);
181 ffurl_closep(&s
->seg_hd
);
185 static int hls_open(URLContext
*h
, const char *uri
, int flags
)
187 HLSContext
*s
= h
->priv_data
;
189 const char *nested_url
;
191 if (flags
& AVIO_FLAG_WRITE
)
192 return AVERROR(ENOSYS
);
196 if (av_strstart(uri
, "hls+", &nested_url
)) {
197 av_strlcpy(s
->playlisturl
, nested_url
, sizeof(s
->playlisturl
));
198 } else if (av_strstart(uri
, "hls://", &nested_url
)) {
199 av_log(h
, AV_LOG_ERROR
,
200 "No nested protocol specified. Specify e.g. hls+http://%s\n",
202 ret
= AVERROR(EINVAL
);
205 av_log(h
, AV_LOG_ERROR
, "Unsupported url %s\n", uri
);
206 ret
= AVERROR(EINVAL
);
209 av_log(h
, AV_LOG_WARNING
,
210 "Using the hls protocol is discouraged, please try using the "
211 "hls demuxer instead. The hls demuxer should be more complete "
212 "and work as well as the protocol implementation. (If not, "
213 "please report it.) To use the demuxer, simply use %s as url.\n",
216 if ((ret
= parse_playlist(h
, s
->playlisturl
)) < 0)
219 if (s
->n_segments
== 0 && s
->n_variants
> 0) {
220 int max_bandwidth
= 0, maxvar
= -1;
221 for (i
= 0; i
< s
->n_variants
; i
++) {
222 if (s
->variants
[i
]->bandwidth
> max_bandwidth
|| i
== 0) {
223 max_bandwidth
= s
->variants
[i
]->bandwidth
;
227 av_strlcpy(s
->playlisturl
, s
->variants
[maxvar
]->url
,
228 sizeof(s
->playlisturl
));
229 if ((ret
= parse_playlist(h
, s
->playlisturl
)) < 0)
233 if (s
->n_segments
== 0) {
234 av_log(h
, AV_LOG_WARNING
, "Empty playlist\n");
238 s
->cur_seq_no
= s
->start_seq_no
;
239 if (!s
->finished
&& s
->n_segments
>= 3)
240 s
->cur_seq_no
= s
->start_seq_no
+ s
->n_segments
- 3;
249 static int hls_read(URLContext
*h
, uint8_t *buf
, int size
)
251 HLSContext
*s
= h
->priv_data
;
254 int64_t reload_interval
;
258 ret
= ffurl_read(s
->seg_hd
, buf
, size
);
263 ffurl_closep(&s
->seg_hd
);
266 reload_interval
= s
->n_segments
> 0 ?
267 s
->segments
[s
->n_segments
- 1]->duration
:
271 int64_t now
= av_gettime_relative();
272 if (now
- s
->last_load_time
>= reload_interval
) {
273 if ((ret
= parse_playlist(h
, s
->playlisturl
)) < 0)
275 /* If we need to reload the playlist again below (if
276 * there's still no more segments), switch to a reload
277 * interval of half the target duration. */
278 reload_interval
= s
->target_duration
/ 2;
281 if (s
->cur_seq_no
< s
->start_seq_no
) {
282 av_log(h
, AV_LOG_WARNING
,
283 "skipping %d segments ahead, expired from playlist\n",
284 s
->start_seq_no
- s
->cur_seq_no
);
285 s
->cur_seq_no
= s
->start_seq_no
;
287 if (s
->cur_seq_no
- s
->start_seq_no
>= s
->n_segments
) {
290 while (av_gettime_relative() - s
->last_load_time
< reload_interval
) {
291 if (ff_check_interrupt(&h
->interrupt_callback
))
297 url
= s
->segments
[s
->cur_seq_no
- s
->start_seq_no
]->url
;
298 av_log(h
, AV_LOG_DEBUG
, "opening %s\n", url
);
299 ret
= ffurl_open_whitelist(&s
->seg_hd
, url
, AVIO_FLAG_READ
,
300 &h
->interrupt_callback
, NULL
,
301 h
->protocol_whitelist
, h
->protocol_blacklist
, h
);
303 if (ff_check_interrupt(&h
->interrupt_callback
))
305 av_log(h
, AV_LOG_WARNING
, "Unable to open %s\n", url
);
312 const URLProtocol ff_hls_protocol
= {
314 .url_open
= hls_open
,
315 .url_read
= hls_read
,
316 .url_close
= hls_close
,
317 .flags
= URL_PROTOCOL_FLAG_NESTED_SCHEME
,
318 .priv_data_size
= sizeof(HLSContext
),