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