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 27 namespace android { 28 29 M3UParser::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 mIsEvent(false) { 37 mInitCheck = parse(data, size); 38 } 39 40 M3UParser::~M3UParser() { 41 } 42 43 status_t M3UParser::initCheck() const { 44 return mInitCheck; 45 } 46 47 bool M3UParser::isExtM3U() const { 48 return mIsExtM3U; 49 } 50 51 bool M3UParser::isVariantPlaylist() const { 52 return mIsVariantPlaylist; 53 } 54 55 bool M3UParser::isComplete() const { 56 return mIsComplete; 57 } 58 59 bool M3UParser::isEvent() const { 60 return mIsEvent; 61 } 62 63 sp<AMessage> M3UParser::meta() { 64 return mMeta; 65 } 66 67 size_t M3UParser::size() { 68 return mItems.size(); 69 } 70 71 bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { 72 if (uri) { 73 uri->clear(); 74 } 75 76 if (meta) { 77 *meta = NULL; 78 } 79 80 if (index >= mItems.size()) { 81 return false; 82 } 83 84 if (uri) { 85 *uri = mItems.itemAt(index).mURI; 86 } 87 88 if (meta) { 89 *meta = mItems.itemAt(index).mMeta; 90 } 91 92 return true; 93 } 94 95 static bool MakeURL(const char *baseURL, const char *url, AString *out) { 96 out->clear(); 97 98 if (strncasecmp("http://", baseURL, 7) 99 && strncasecmp("https://", baseURL, 8) 100 && strncasecmp("file://", baseURL, 7)) { 101 // Base URL must be absolute 102 return false; 103 } 104 105 if (!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) { 106 // "url" is already an absolute URL, ignore base URL. 107 out->setTo(url); 108 109 ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 110 111 return true; 112 } 113 114 if (url[0] == '/') { 115 // URL is an absolute path. 116 117 char *protocolEnd = strstr(baseURL, "//") + 2; 118 char *pathStart = strchr(protocolEnd, '/'); 119 120 if (pathStart != NULL) { 121 out->setTo(baseURL, pathStart - baseURL); 122 } else { 123 out->setTo(baseURL); 124 } 125 126 out->append(url); 127 } else { 128 // URL is a relative path 129 130 size_t n = strlen(baseURL); 131 if (baseURL[n - 1] == '/') { 132 out->setTo(baseURL); 133 out->append(url); 134 } else { 135 const char *slashPos = strrchr(baseURL, '/'); 136 137 if (slashPos > &baseURL[6]) { 138 out->setTo(baseURL, slashPos - baseURL); 139 } else { 140 out->setTo(baseURL); 141 } 142 143 out->append("/"); 144 out->append(url); 145 } 146 } 147 148 ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 149 150 return true; 151 } 152 153 status_t M3UParser::parse(const void *_data, size_t size) { 154 int32_t lineNo = 0; 155 156 sp<AMessage> itemMeta; 157 158 const char *data = (const char *)_data; 159 size_t offset = 0; 160 uint64_t segmentRangeOffset = 0; 161 while (offset < size) { 162 size_t offsetLF = offset; 163 while (offsetLF < size && data[offsetLF] != '\n') { 164 ++offsetLF; 165 } 166 167 AString line; 168 if (offsetLF > offset && data[offsetLF - 1] == '\r') { 169 line.setTo(&data[offset], offsetLF - offset - 1); 170 } else { 171 line.setTo(&data[offset], offsetLF - offset); 172 } 173 174 // ALOGI("#%s#", line.c_str()); 175 176 if (line.empty()) { 177 offset = offsetLF + 1; 178 continue; 179 } 180 181 if (lineNo == 0 && line == "#EXTM3U") { 182 mIsExtM3U = true; 183 } 184 185 if (mIsExtM3U) { 186 status_t err = OK; 187 188 if (line.startsWith("#EXT-X-TARGETDURATION")) { 189 if (mIsVariantPlaylist) { 190 return ERROR_MALFORMED; 191 } 192 err = parseMetaData(line, &mMeta, "target-duration"); 193 } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { 194 if (mIsVariantPlaylist) { 195 return ERROR_MALFORMED; 196 } 197 err = parseMetaData(line, &mMeta, "media-sequence"); 198 } else if (line.startsWith("#EXT-X-KEY")) { 199 if (mIsVariantPlaylist) { 200 return ERROR_MALFORMED; 201 } 202 err = parseCipherInfo(line, &itemMeta, mBaseURI); 203 } else if (line.startsWith("#EXT-X-ENDLIST")) { 204 mIsComplete = true; 205 } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) { 206 mIsEvent = true; 207 } else if (line.startsWith("#EXTINF")) { 208 if (mIsVariantPlaylist) { 209 return ERROR_MALFORMED; 210 } 211 err = parseMetaDataDuration(line, &itemMeta, "durationUs"); 212 } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { 213 if (mIsVariantPlaylist) { 214 return ERROR_MALFORMED; 215 } 216 if (itemMeta == NULL) { 217 itemMeta = new AMessage; 218 } 219 itemMeta->setInt32("discontinuity", true); 220 } else if (line.startsWith("#EXT-X-STREAM-INF")) { 221 if (mMeta != NULL) { 222 return ERROR_MALFORMED; 223 } 224 mIsVariantPlaylist = true; 225 err = parseStreamInf(line, &itemMeta); 226 } else if (line.startsWith("#EXT-X-BYTERANGE")) { 227 if (mIsVariantPlaylist) { 228 return ERROR_MALFORMED; 229 } 230 231 uint64_t length, offset; 232 err = parseByteRange(line, segmentRangeOffset, &length, &offset); 233 234 if (err == OK) { 235 if (itemMeta == NULL) { 236 itemMeta = new AMessage; 237 } 238 239 itemMeta->setInt64("range-offset", offset); 240 itemMeta->setInt64("range-length", length); 241 242 segmentRangeOffset = offset + length; 243 } 244 } 245 246 if (err != OK) { 247 return err; 248 } 249 } 250 251 if (!line.startsWith("#")) { 252 if (!mIsVariantPlaylist) { 253 int64_t durationUs; 254 if (itemMeta == NULL 255 || !itemMeta->findInt64("durationUs", &durationUs)) { 256 return ERROR_MALFORMED; 257 } 258 } 259 260 mItems.push(); 261 Item *item = &mItems.editItemAt(mItems.size() - 1); 262 263 CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); 264 265 item->mMeta = itemMeta; 266 267 itemMeta.clear(); 268 } 269 270 offset = offsetLF + 1; 271 ++lineNo; 272 } 273 274 return OK; 275 } 276 277 // static 278 status_t M3UParser::parseMetaData( 279 const AString &line, sp<AMessage> *meta, const char *key) { 280 ssize_t colonPos = line.find(":"); 281 282 if (colonPos < 0) { 283 return ERROR_MALFORMED; 284 } 285 286 int32_t x; 287 status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); 288 289 if (err != OK) { 290 return err; 291 } 292 293 if (meta->get() == NULL) { 294 *meta = new AMessage; 295 } 296 (*meta)->setInt32(key, x); 297 298 return OK; 299 } 300 301 // static 302 status_t M3UParser::parseMetaDataDuration( 303 const AString &line, sp<AMessage> *meta, const char *key) { 304 ssize_t colonPos = line.find(":"); 305 306 if (colonPos < 0) { 307 return ERROR_MALFORMED; 308 } 309 310 double x; 311 status_t err = ParseDouble(line.c_str() + colonPos + 1, &x); 312 313 if (err != OK) { 314 return err; 315 } 316 317 if (meta->get() == NULL) { 318 *meta = new AMessage; 319 } 320 (*meta)->setInt64(key, (int64_t)x * 1E6); 321 322 return OK; 323 } 324 325 // static 326 status_t M3UParser::parseStreamInf( 327 const AString &line, sp<AMessage> *meta) { 328 ssize_t colonPos = line.find(":"); 329 330 if (colonPos < 0) { 331 return ERROR_MALFORMED; 332 } 333 334 size_t offset = colonPos + 1; 335 336 while (offset < line.size()) { 337 ssize_t end = line.find(",", offset); 338 if (end < 0) { 339 end = line.size(); 340 } 341 342 AString attr(line, offset, end - offset); 343 attr.trim(); 344 345 offset = end + 1; 346 347 ssize_t equalPos = attr.find("="); 348 if (equalPos < 0) { 349 continue; 350 } 351 352 AString key(attr, 0, equalPos); 353 key.trim(); 354 355 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 356 val.trim(); 357 358 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 359 360 if (!strcasecmp("bandwidth", key.c_str())) { 361 const char *s = val.c_str(); 362 char *end; 363 unsigned long x = strtoul(s, &end, 10); 364 365 if (end == s || *end != '\0') { 366 // malformed 367 continue; 368 } 369 370 if (meta->get() == NULL) { 371 *meta = new AMessage; 372 } 373 (*meta)->setInt32("bandwidth", x); 374 } 375 } 376 377 return OK; 378 } 379 380 // Find the next occurence of the character "what" at or after "offset", 381 // but ignore occurences between quotation marks. 382 // Return the index of the occurrence or -1 if not found. 383 static ssize_t FindNextUnquoted( 384 const AString &line, char what, size_t offset) { 385 CHECK_NE((int)what, (int)'"'); 386 387 bool quoted = false; 388 while (offset < line.size()) { 389 char c = line.c_str()[offset]; 390 391 if (c == '"') { 392 quoted = !quoted; 393 } else if (c == what && !quoted) { 394 return offset; 395 } 396 397 ++offset; 398 } 399 400 return -1; 401 } 402 403 // static 404 status_t M3UParser::parseCipherInfo( 405 const AString &line, sp<AMessage> *meta, const AString &baseURI) { 406 ssize_t colonPos = line.find(":"); 407 408 if (colonPos < 0) { 409 return ERROR_MALFORMED; 410 } 411 412 size_t offset = colonPos + 1; 413 414 while (offset < line.size()) { 415 ssize_t end = FindNextUnquoted(line, ',', offset); 416 if (end < 0) { 417 end = line.size(); 418 } 419 420 AString attr(line, offset, end - offset); 421 attr.trim(); 422 423 offset = end + 1; 424 425 ssize_t equalPos = attr.find("="); 426 if (equalPos < 0) { 427 continue; 428 } 429 430 AString key(attr, 0, equalPos); 431 key.trim(); 432 433 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 434 val.trim(); 435 436 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 437 438 key.tolower(); 439 440 if (key == "method" || key == "uri" || key == "iv") { 441 if (meta->get() == NULL) { 442 *meta = new AMessage; 443 } 444 445 if (key == "uri") { 446 if (val.size() >= 2 447 && val.c_str()[0] == '"' 448 && val.c_str()[val.size() - 1] == '"') { 449 // Remove surrounding quotes. 450 AString tmp(val, 1, val.size() - 2); 451 val = tmp; 452 } 453 454 AString absURI; 455 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) { 456 val = absURI; 457 } else { 458 ALOGE("failed to make absolute url for '%s'.", 459 val.c_str()); 460 } 461 } 462 463 key.insert(AString("cipher-"), 0); 464 465 (*meta)->setString(key.c_str(), val.c_str(), val.size()); 466 } 467 } 468 469 return OK; 470 } 471 472 // static 473 status_t M3UParser::parseByteRange( 474 const AString &line, uint64_t curOffset, 475 uint64_t *length, uint64_t *offset) { 476 ssize_t colonPos = line.find(":"); 477 478 if (colonPos < 0) { 479 return ERROR_MALFORMED; 480 } 481 482 ssize_t atPos = line.find("@", colonPos + 1); 483 484 AString lenStr; 485 if (atPos < 0) { 486 lenStr = AString(line, colonPos + 1, line.size() - colonPos - 1); 487 } else { 488 lenStr = AString(line, colonPos + 1, atPos - colonPos - 1); 489 } 490 491 lenStr.trim(); 492 493 const char *s = lenStr.c_str(); 494 char *end; 495 *length = strtoull(s, &end, 10); 496 497 if (s == end || *end != '\0') { 498 return ERROR_MALFORMED; 499 } 500 501 if (atPos >= 0) { 502 AString offStr = AString(line, atPos + 1, line.size() - atPos - 1); 503 offStr.trim(); 504 505 const char *s = offStr.c_str(); 506 *offset = strtoull(s, &end, 10); 507 508 if (s == end || *end != '\0') { 509 return ERROR_MALFORMED; 510 } 511 } else { 512 *offset = curOffset; 513 } 514 515 return OK; 516 } 517 518 // static 519 status_t M3UParser::ParseInt32(const char *s, int32_t *x) { 520 char *end; 521 long lval = strtol(s, &end, 10); 522 523 if (end == s || (*end != '\0' && *end != ',')) { 524 return ERROR_MALFORMED; 525 } 526 527 *x = (int32_t)lval; 528 529 return OK; 530 } 531 532 // static 533 status_t M3UParser::ParseDouble(const char *s, double *x) { 534 char *end; 535 double dval = strtod(s, &end); 536 537 if (end == s || (*end != '\0' && *end != ',')) { 538 return ERROR_MALFORMED; 539 } 540 541 *x = dval; 542 543 return OK; 544 } 545 546 } // namespace android 547