M3UParser.cpp revision 0f30bd90272c818aa37c0bb22d22eaa7d3689879
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "M3UParser" 19#include <utils/Log.h> 20 21#include "include/M3UParser.h" 22 23#include <media/stagefright/foundation/ADebug.h> 24#include <media/stagefright/foundation/AMessage.h> 25#include <media/stagefright/MediaErrors.h> 26 27namespace android { 28 29M3UParser::M3UParser( 30 const char *baseURI, const void *data, size_t size) 31 : mInitCheck(NO_INIT), 32 mBaseURI(baseURI), 33 mIsExtM3U(false), 34 mIsVariantPlaylist(false), 35 mIsComplete(false) { 36 mInitCheck = parse(data, size); 37} 38 39M3UParser::~M3UParser() { 40} 41 42status_t M3UParser::initCheck() const { 43 return mInitCheck; 44} 45 46bool M3UParser::isExtM3U() const { 47 return mIsExtM3U; 48} 49 50bool M3UParser::isVariantPlaylist() const { 51 return mIsVariantPlaylist; 52} 53 54bool M3UParser::isComplete() const { 55 return mIsComplete; 56} 57 58sp<AMessage> M3UParser::meta() { 59 return mMeta; 60} 61 62size_t M3UParser::size() { 63 return mItems.size(); 64} 65 66bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { 67 if (uri) { 68 uri->clear(); 69 } 70 71 if (meta) { 72 *meta = NULL; 73 } 74 75 if (index >= mItems.size()) { 76 return false; 77 } 78 79 if (uri) { 80 *uri = mItems.itemAt(index).mURI; 81 } 82 83 if (meta) { 84 *meta = mItems.itemAt(index).mMeta; 85 } 86 87 return true; 88} 89 90static bool MakeURL(const char *baseURL, const char *url, AString *out) { 91 out->clear(); 92 93 if (strncasecmp("http://", baseURL, 7) 94 && strncasecmp("https://", baseURL, 8) 95 && strncasecmp("file://", baseURL, 7)) { 96 // Base URL must be absolute 97 return false; 98 } 99 100 if (!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) { 101 // "url" is already an absolute URL, ignore base URL. 102 out->setTo(url); 103 104 LOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 105 106 return true; 107 } 108 109 size_t n = strlen(baseURL); 110 if (baseURL[n - 1] == '/') { 111 out->setTo(baseURL); 112 out->append(url); 113 } else { 114 const char *slashPos = strrchr(baseURL, '/'); 115 116 if (slashPos > &baseURL[6]) { 117 out->setTo(baseURL, slashPos - baseURL); 118 } else { 119 out->setTo(baseURL); 120 } 121 122 out->append("/"); 123 out->append(url); 124 } 125 126 LOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 127 128 return true; 129} 130 131status_t M3UParser::parse(const void *_data, size_t size) { 132 int32_t lineNo = 0; 133 134 sp<AMessage> itemMeta; 135 136 const char *data = (const char *)_data; 137 size_t offset = 0; 138 while (offset < size) { 139 size_t offsetLF = offset; 140 while (offsetLF < size && data[offsetLF] != '\n') { 141 ++offsetLF; 142 } 143 if (offsetLF >= size) { 144 break; 145 } 146 147 AString line; 148 if (offsetLF > offset && data[offsetLF - 1] == '\r') { 149 line.setTo(&data[offset], offsetLF - offset - 1); 150 } else { 151 line.setTo(&data[offset], offsetLF - offset); 152 } 153 154 // LOGI("#%s#", line.c_str()); 155 156 if (line.empty()) { 157 offset = offsetLF + 1; 158 continue; 159 } 160 161 if (lineNo == 0 && line == "#EXTM3U") { 162 mIsExtM3U = true; 163 } 164 165 if (mIsExtM3U) { 166 status_t err = OK; 167 168 if (line.startsWith("#EXT-X-TARGETDURATION")) { 169 if (mIsVariantPlaylist) { 170 return ERROR_MALFORMED; 171 } 172 err = parseMetaData(line, &mMeta, "target-duration"); 173 } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { 174 if (mIsVariantPlaylist) { 175 return ERROR_MALFORMED; 176 } 177 err = parseMetaData(line, &mMeta, "media-sequence"); 178 } else if (line.startsWith("#EXT-X-KEY")) { 179 if (mIsVariantPlaylist) { 180 return ERROR_MALFORMED; 181 } 182 err = parseCipherInfo(line, &itemMeta, mBaseURI); 183 } else if (line.startsWith("#EXT-X-ENDLIST")) { 184 mIsComplete = true; 185 } else if (line.startsWith("#EXTINF")) { 186 if (mIsVariantPlaylist) { 187 return ERROR_MALFORMED; 188 } 189 err = parseMetaDataDuration(line, &itemMeta, "durationUs"); 190 } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { 191 if (mIsVariantPlaylist) { 192 return ERROR_MALFORMED; 193 } 194 if (itemMeta == NULL) { 195 itemMeta = new AMessage; 196 } 197 itemMeta->setInt32("discontinuity", true); 198 } else if (line.startsWith("#EXT-X-STREAM-INF")) { 199 if (mMeta != NULL) { 200 return ERROR_MALFORMED; 201 } 202 mIsVariantPlaylist = true; 203 err = parseStreamInf(line, &itemMeta); 204 } 205 206 if (err != OK) { 207 return err; 208 } 209 } 210 211 if (!line.startsWith("#")) { 212 if (!mIsVariantPlaylist) { 213 int64_t durationUs; 214 if (itemMeta == NULL 215 || !itemMeta->findInt64("durationUs", &durationUs)) { 216 return ERROR_MALFORMED; 217 } 218 } 219 220 mItems.push(); 221 Item *item = &mItems.editItemAt(mItems.size() - 1); 222 223 CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); 224 225 item->mMeta = itemMeta; 226 227 itemMeta.clear(); 228 } 229 230 offset = offsetLF + 1; 231 ++lineNo; 232 } 233 234 return OK; 235} 236 237// static 238status_t M3UParser::parseMetaData( 239 const AString &line, sp<AMessage> *meta, const char *key) { 240 ssize_t colonPos = line.find(":"); 241 242 if (colonPos < 0) { 243 return ERROR_MALFORMED; 244 } 245 246 int32_t x; 247 status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); 248 249 if (err != OK) { 250 return err; 251 } 252 253 if (meta->get() == NULL) { 254 *meta = new AMessage; 255 } 256 (*meta)->setInt32(key, x); 257 258 return OK; 259} 260 261// static 262status_t M3UParser::parseMetaDataDuration( 263 const AString &line, sp<AMessage> *meta, const char *key) { 264 ssize_t colonPos = line.find(":"); 265 266 if (colonPos < 0) { 267 return ERROR_MALFORMED; 268 } 269 270 double x; 271 status_t err = ParseDouble(line.c_str() + colonPos + 1, &x); 272 273 if (err != OK) { 274 return err; 275 } 276 277 if (meta->get() == NULL) { 278 *meta = new AMessage; 279 } 280 (*meta)->setInt64(key, (int64_t)x * 1E6); 281 282 return OK; 283} 284 285// static 286status_t M3UParser::parseStreamInf( 287 const AString &line, sp<AMessage> *meta) { 288 ssize_t colonPos = line.find(":"); 289 290 if (colonPos < 0) { 291 return ERROR_MALFORMED; 292 } 293 294 size_t offset = colonPos + 1; 295 296 while (offset < line.size()) { 297 ssize_t end = line.find(",", offset); 298 if (end < 0) { 299 end = line.size(); 300 } 301 302 AString attr(line, offset, end - offset); 303 attr.trim(); 304 305 offset = end + 1; 306 307 ssize_t equalPos = attr.find("="); 308 if (equalPos < 0) { 309 continue; 310 } 311 312 AString key(attr, 0, equalPos); 313 key.trim(); 314 315 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 316 val.trim(); 317 318 LOGV("key=%s value=%s", key.c_str(), val.c_str()); 319 320 if (!strcasecmp("bandwidth", key.c_str())) { 321 const char *s = val.c_str(); 322 char *end; 323 unsigned long x = strtoul(s, &end, 10); 324 325 if (end == s || *end != '\0') { 326 // malformed 327 continue; 328 } 329 330 if (meta->get() == NULL) { 331 *meta = new AMessage; 332 } 333 (*meta)->setInt32("bandwidth", x); 334 } 335 } 336 337 return OK; 338} 339 340// Find the next occurence of the character "what" at or after "offset", 341// but ignore occurences between quotation marks. 342// Return the index of the occurrence or -1 if not found. 343static ssize_t FindNextUnquoted( 344 const AString &line, char what, size_t offset) { 345 CHECK_NE((int)what, (int)'"'); 346 347 bool quoted = false; 348 while (offset < line.size()) { 349 char c = line.c_str()[offset]; 350 351 if (c == '"') { 352 quoted = !quoted; 353 } else if (c == what && !quoted) { 354 return offset; 355 } 356 357 ++offset; 358 } 359 360 return -1; 361} 362 363// static 364status_t M3UParser::parseCipherInfo( 365 const AString &line, sp<AMessage> *meta, const AString &baseURI) { 366 ssize_t colonPos = line.find(":"); 367 368 if (colonPos < 0) { 369 return ERROR_MALFORMED; 370 } 371 372 size_t offset = colonPos + 1; 373 374 while (offset < line.size()) { 375 ssize_t end = FindNextUnquoted(line, ',', offset); 376 if (end < 0) { 377 end = line.size(); 378 } 379 380 AString attr(line, offset, end - offset); 381 attr.trim(); 382 383 offset = end + 1; 384 385 ssize_t equalPos = attr.find("="); 386 if (equalPos < 0) { 387 continue; 388 } 389 390 AString key(attr, 0, equalPos); 391 key.trim(); 392 393 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 394 val.trim(); 395 396 LOGV("key=%s value=%s", key.c_str(), val.c_str()); 397 398 key.tolower(); 399 400 if (key == "method" || key == "uri" || key == "iv") { 401 if (meta->get() == NULL) { 402 *meta = new AMessage; 403 } 404 405 if (key == "uri") { 406 if (val.size() >= 2 407 && val.c_str()[0] == '"' 408 && val.c_str()[val.size() - 1] == '"') { 409 // Remove surrounding quotes. 410 AString tmp(val, 1, val.size() - 2); 411 val = tmp; 412 } 413 414 AString absURI; 415 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) { 416 val = absURI; 417 } else { 418 LOGE("failed to make absolute url for '%s'.", 419 val.c_str()); 420 } 421 } 422 423 key.insert(AString("cipher-"), 0); 424 425 (*meta)->setString(key.c_str(), val.c_str(), val.size()); 426 } 427 } 428 429 return OK; 430} 431 432// static 433status_t M3UParser::ParseInt32(const char *s, int32_t *x) { 434 char *end; 435 long lval = strtol(s, &end, 10); 436 437 if (end == s || (*end != '\0' && *end != ',')) { 438 return ERROR_MALFORMED; 439 } 440 441 *x = (int32_t)lval; 442 443 return OK; 444} 445 446// static 447status_t M3UParser::ParseDouble(const char *s, double *x) { 448 char *end; 449 double dval = strtod(s, &end); 450 451 if (end == s || (*end != '\0' && *end != ',')) { 452 return ERROR_MALFORMED; 453 } 454 455 *x = dval; 456 457 return OK; 458} 459 460} // namespace android 461