blob: 2b19ed0cf67a88d0fa6aa6e9ffd73f844eab7f4e
1 | /* |
2 | * Apple HTTP Live Streaming Protocol Handler |
3 | * Copyright (c) 2010 Martin Storsjo |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
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. |
11 | * |
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. |
16 | * |
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 |
20 | */ |
21 | |
22 | /** |
23 | * @file |
24 | * Apple HTTP Live Streaming Protocol Handler |
25 | * http://tools.ietf.org/html/draft-pantos-http-live-streaming |
26 | */ |
27 | |
28 | #include "libavutil/avstring.h" |
29 | #include "libavutil/time.h" |
30 | #include "avformat.h" |
31 | #include "avio_internal.h" |
32 | #include "internal.h" |
33 | #include "url.h" |
34 | #include "version.h" |
35 | |
36 | /* |
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 |
42 | * playlists. |
43 | * |
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. |
46 | */ |
47 | |
48 | struct segment { |
49 | int64_t duration; |
50 | char url[MAX_URL_SIZE]; |
51 | }; |
52 | |
53 | struct variant { |
54 | int bandwidth; |
55 | char url[MAX_URL_SIZE]; |
56 | }; |
57 | |
58 | typedef struct HLSContext { |
59 | char playlisturl[MAX_URL_SIZE]; |
60 | int64_t target_duration; |
61 | int start_seq_no; |
62 | int finished; |
63 | int n_segments; |
64 | struct segment **segments; |
65 | int n_variants; |
66 | struct variant **variants; |
67 | int cur_seq_no; |
68 | URLContext *seg_hd; |
69 | int64_t last_load_time; |
70 | } HLSContext; |
71 | |
72 | static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) |
73 | { |
74 | int len = ff_get_line(s, buf, maxlen); |
75 | while (len > 0 && av_isspace(buf[len - 1])) |
76 | buf[--len] = '\0'; |
77 | return len; |
78 | } |
79 | |
80 | static void free_segment_list(HLSContext *s) |
81 | { |
82 | int i; |
83 | for (i = 0; i < s->n_segments; i++) |
84 | av_freep(&s->segments[i]); |
85 | av_freep(&s->segments); |
86 | s->n_segments = 0; |
87 | } |
88 | |
89 | static void free_variant_list(HLSContext *s) |
90 | { |
91 | int i; |
92 | for (i = 0; i < s->n_variants; i++) |
93 | av_freep(&s->variants[i]); |
94 | av_freep(&s->variants); |
95 | s->n_variants = 0; |
96 | } |
97 | |
98 | struct variant_info { |
99 | char bandwidth[20]; |
100 | }; |
101 | |
102 | static void handle_variant_args(struct variant_info *info, const char *key, |
103 | int key_len, char **dest, int *dest_len) |
104 | { |
105 | if (!strncmp(key, "BANDWIDTH=", key_len)) { |
106 | *dest = info->bandwidth; |
107 | *dest_len = sizeof(info->bandwidth); |
108 | } |
109 | } |
110 | |
111 | static int parse_playlist(URLContext *h, const char *url) |
112 | { |
113 | HLSContext *s = h->priv_data; |
114 | AVIOContext *in; |
115 | int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0; |
116 | int64_t duration = 0; |
117 | char line[1024]; |
118 | const char *ptr; |
119 | |
120 | if ((ret = ffio_open_whitelist(&in, url, AVIO_FLAG_READ, |
121 | &h->interrupt_callback, NULL, |
122 | h->protocol_whitelist, h->protocol_blacklist)) < 0) |
123 | return ret; |
124 | |
125 | read_chomp_line(in, line, sizeof(line)); |
126 | if (strcmp(line, "#EXTM3U")) { |
127 | ret = AVERROR_INVALIDDATA; |
128 | goto fail; |
129 | } |
130 | |
131 | free_segment_list(s); |
132 | s->finished = 0; |
133 | while (!avio_feof(in)) { |
134 | read_chomp_line(in, line, sizeof(line)); |
135 | if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { |
136 | struct variant_info info = {{0}}; |
137 | is_variant = 1; |
138 | ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, |
139 | &info); |
140 | bandwidth = atoi(info.bandwidth); |
141 | } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { |
142 | s->target_duration = atoi(ptr) * AV_TIME_BASE; |
143 | } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { |
144 | s->start_seq_no = atoi(ptr); |
145 | } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { |
146 | s->finished = 1; |
147 | } else if (av_strstart(line, "#EXTINF:", &ptr)) { |
148 | is_segment = 1; |
149 | duration = atof(ptr) * AV_TIME_BASE; |
150 | } else if (av_strstart(line, "#", NULL)) { |
151 | continue; |
152 | } else if (line[0]) { |
153 | if (is_segment) { |
154 | struct segment *seg = av_malloc(sizeof(struct segment)); |
155 | if (!seg) { |
156 | ret = AVERROR(ENOMEM); |
157 | goto fail; |
158 | } |
159 | seg->duration = duration; |
160 | ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); |
161 | dynarray_add(&s->segments, &s->n_segments, seg); |
162 | is_segment = 0; |
163 | } else if (is_variant) { |
164 | struct variant *var = av_malloc(sizeof(struct variant)); |
165 | if (!var) { |
166 | ret = AVERROR(ENOMEM); |
167 | goto fail; |
168 | } |
169 | var->bandwidth = bandwidth; |
170 | ff_make_absolute_url(var->url, sizeof(var->url), url, line); |
171 | dynarray_add(&s->variants, &s->n_variants, var); |
172 | is_variant = 0; |
173 | } |
174 | } |
175 | } |
176 | s->last_load_time = av_gettime_relative(); |
177 | |
178 | fail: |
179 | avio_close(in); |
180 | return ret; |
181 | } |
182 | |
183 | static int hls_close(URLContext *h) |
184 | { |
185 | HLSContext *s = h->priv_data; |
186 | |
187 | free_segment_list(s); |
188 | free_variant_list(s); |
189 | ffurl_close(s->seg_hd); |
190 | return 0; |
191 | } |
192 | |
193 | static int hls_open(URLContext *h, const char *uri, int flags) |
194 | { |
195 | HLSContext *s = h->priv_data; |
196 | int ret, i; |
197 | const char *nested_url; |
198 | |
199 | if (flags & AVIO_FLAG_WRITE) |
200 | return AVERROR(ENOSYS); |
201 | |
202 | h->is_streamed = 1; |
203 | |
204 | if (av_strstart(uri, "hls+", &nested_url)) { |
205 | av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); |
206 | } else if (av_strstart(uri, "hls://", &nested_url)) { |
207 | av_log(h, AV_LOG_ERROR, |
208 | "No nested protocol specified. Specify e.g. hls+http://%s\n", |
209 | nested_url); |
210 | ret = AVERROR(EINVAL); |
211 | goto fail; |
212 | } else { |
213 | av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); |
214 | ret = AVERROR(EINVAL); |
215 | goto fail; |
216 | } |
217 | av_log(h, AV_LOG_WARNING, |
218 | "Using the hls protocol is discouraged, please try using the " |
219 | "hls demuxer instead. The hls demuxer should be more complete " |
220 | "and work as well as the protocol implementation. (If not, " |
221 | "please report it.) To use the demuxer, simply use %s as url.\n", |
222 | s->playlisturl); |
223 | |
224 | if ((ret = parse_playlist(h, s->playlisturl)) < 0) |
225 | goto fail; |
226 | |
227 | if (s->n_segments == 0 && s->n_variants > 0) { |
228 | int max_bandwidth = 0, maxvar = -1; |
229 | for (i = 0; i < s->n_variants; i++) { |
230 | if (s->variants[i]->bandwidth > max_bandwidth || i == 0) { |
231 | max_bandwidth = s->variants[i]->bandwidth; |
232 | maxvar = i; |
233 | } |
234 | } |
235 | av_strlcpy(s->playlisturl, s->variants[maxvar]->url, |
236 | sizeof(s->playlisturl)); |
237 | if ((ret = parse_playlist(h, s->playlisturl)) < 0) |
238 | goto fail; |
239 | } |
240 | |
241 | if (s->n_segments == 0) { |
242 | av_log(h, AV_LOG_WARNING, "Empty playlist\n"); |
243 | ret = AVERROR(EIO); |
244 | goto fail; |
245 | } |
246 | s->cur_seq_no = s->start_seq_no; |
247 | if (!s->finished && s->n_segments >= 3) |
248 | s->cur_seq_no = s->start_seq_no + s->n_segments - 3; |
249 | |
250 | return 0; |
251 | |
252 | fail: |
253 | hls_close(h); |
254 | return ret; |
255 | } |
256 | |
257 | static int hls_read(URLContext *h, uint8_t *buf, int size) |
258 | { |
259 | HLSContext *s = h->priv_data; |
260 | const char *url; |
261 | int ret; |
262 | int64_t reload_interval; |
263 | |
264 | start: |
265 | if (s->seg_hd) { |
266 | ret = ffurl_read(s->seg_hd, buf, size); |
267 | if (ret > 0) |
268 | return ret; |
269 | } |
270 | if (s->seg_hd) { |
271 | ffurl_close(s->seg_hd); |
272 | s->seg_hd = NULL; |
273 | s->cur_seq_no++; |
274 | } |
275 | reload_interval = s->n_segments > 0 ? |
276 | s->segments[s->n_segments - 1]->duration : |
277 | s->target_duration; |
278 | retry: |
279 | if (!s->finished) { |
280 | int64_t now = av_gettime_relative(); |
281 | if (now - s->last_load_time >= reload_interval) { |
282 | if ((ret = parse_playlist(h, s->playlisturl)) < 0) |
283 | return ret; |
284 | /* If we need to reload the playlist again below (if |
285 | * there's still no more segments), switch to a reload |
286 | * interval of half the target duration. */ |
287 | reload_interval = s->target_duration / 2; |
288 | } |
289 | } |
290 | if (s->cur_seq_no < s->start_seq_no) { |
291 | av_log(h, AV_LOG_WARNING, |
292 | "skipping %d segments ahead, expired from playlist\n", |
293 | s->start_seq_no - s->cur_seq_no); |
294 | s->cur_seq_no = s->start_seq_no; |
295 | } |
296 | if (s->cur_seq_no - s->start_seq_no >= s->n_segments) { |
297 | if (s->finished) |
298 | return AVERROR_EOF; |
299 | while (av_gettime_relative() - s->last_load_time < reload_interval) { |
300 | if (ff_check_interrupt(&h->interrupt_callback)) |
301 | return AVERROR_EXIT; |
302 | av_usleep(100*1000); |
303 | } |
304 | goto retry; |
305 | } |
306 | url = s->segments[s->cur_seq_no - s->start_seq_no]->url, |
307 | av_log(h, AV_LOG_DEBUG, "opening %s\n", url); |
308 | ret = ffurl_open_whitelist(&s->seg_hd, url, AVIO_FLAG_READ, |
309 | &h->interrupt_callback, NULL, |
310 | h->protocol_whitelist, h->protocol_blacklist, h); |
311 | if (ret < 0) { |
312 | if (ff_check_interrupt(&h->interrupt_callback)) |
313 | return AVERROR_EXIT; |
314 | av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url); |
315 | s->cur_seq_no++; |
316 | goto retry; |
317 | } |
318 | goto start; |
319 | } |
320 | |
321 | const URLProtocol ff_hls_protocol = { |
322 | .name = "hls", |
323 | .url_open = hls_open, |
324 | .url_read = hls_read, |
325 | .url_close = hls_close, |
326 | .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, |
327 | .priv_data_size = sizeof(HLSContext), |
328 | }; |
329 |