M3UParser.cpp revision 14f7672b5d450ed26a06fd3bb3ce045ea78b11b2
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 "M3UParser.h" 22 23#include <cutils/properties.h> 24#include <media/stagefright/foundation/ADebug.h> 25#include <media/stagefright/foundation/AMessage.h> 26#include <media/stagefright/MediaErrors.h> 27 28namespace android { 29 30struct M3UParser::MediaGroup : public RefBase { 31 enum Type { 32 TYPE_AUDIO, 33 TYPE_VIDEO, 34 TYPE_SUBS, 35 }; 36 37 enum FlagBits { 38 FLAG_AUTOSELECT = 1, 39 FLAG_DEFAULT = 2, 40 FLAG_FORCED = 4, 41 FLAG_HAS_LANGUAGE = 8, 42 FLAG_HAS_URI = 16, 43 }; 44 45 MediaGroup(Type type); 46 47 Type type() const; 48 49 status_t addMedia( 50 const char *name, 51 const char *uri, 52 const char *language, 53 uint32_t flags); 54 55 bool getActiveURI(AString *uri) const; 56 57 void pickRandomMediaItems(); 58 59protected: 60 virtual ~MediaGroup(); 61 62private: 63 struct Media { 64 AString mName; 65 AString mURI; 66 AString mLanguage; 67 uint32_t mFlags; 68 }; 69 70 Type mType; 71 Vector<Media> mMediaItems; 72 73 ssize_t mSelectedIndex; 74 75 DISALLOW_EVIL_CONSTRUCTORS(MediaGroup); 76}; 77 78M3UParser::MediaGroup::MediaGroup(Type type) 79 : mType(type), 80 mSelectedIndex(-1) { 81} 82 83M3UParser::MediaGroup::~MediaGroup() { 84} 85 86M3UParser::MediaGroup::Type M3UParser::MediaGroup::type() const { 87 return mType; 88} 89 90status_t M3UParser::MediaGroup::addMedia( 91 const char *name, 92 const char *uri, 93 const char *language, 94 uint32_t flags) { 95 mMediaItems.push(); 96 Media &item = mMediaItems.editItemAt(mMediaItems.size() - 1); 97 98 item.mName = name; 99 100 if (uri) { 101 item.mURI = uri; 102 } 103 104 if (language) { 105 item.mLanguage = language; 106 } 107 108 item.mFlags = flags; 109 110 return OK; 111} 112 113void M3UParser::MediaGroup::pickRandomMediaItems() { 114#if 1 115 switch (mType) { 116 case TYPE_AUDIO: 117 { 118 char value[PROPERTY_VALUE_MAX]; 119 if (property_get("media.httplive.audio-index", value, NULL)) { 120 char *end; 121 mSelectedIndex = strtoul(value, &end, 10); 122 CHECK(end > value && *end == '\0'); 123 124 if (mSelectedIndex >= mMediaItems.size()) { 125 mSelectedIndex = mMediaItems.size() - 1; 126 } 127 } else { 128 mSelectedIndex = 0; 129 } 130 break; 131 } 132 133 case TYPE_VIDEO: 134 { 135 mSelectedIndex = 0; 136 break; 137 } 138 139 case TYPE_SUBS: 140 { 141 mSelectedIndex = -1; 142 break; 143 } 144 145 default: 146 TRESPASS(); 147 } 148#else 149 mSelectedIndex = (rand() * mMediaItems.size()) / RAND_MAX; 150#endif 151} 152 153bool M3UParser::MediaGroup::getActiveURI(AString *uri) const { 154 for (size_t i = 0; i < mMediaItems.size(); ++i) { 155 if (mSelectedIndex >= 0 && i == (size_t)mSelectedIndex) { 156 const Media &item = mMediaItems.itemAt(i); 157 158 *uri = item.mURI; 159 return true; 160 } 161 } 162 163 return false; 164} 165 166//////////////////////////////////////////////////////////////////////////////// 167 168M3UParser::M3UParser( 169 const char *baseURI, const void *data, size_t size) 170 : mInitCheck(NO_INIT), 171 mBaseURI(baseURI), 172 mIsExtM3U(false), 173 mIsVariantPlaylist(false), 174 mIsComplete(false), 175 mIsEvent(false) { 176 mInitCheck = parse(data, size); 177} 178 179M3UParser::~M3UParser() { 180} 181 182status_t M3UParser::initCheck() const { 183 return mInitCheck; 184} 185 186bool M3UParser::isExtM3U() const { 187 return mIsExtM3U; 188} 189 190bool M3UParser::isVariantPlaylist() const { 191 return mIsVariantPlaylist; 192} 193 194bool M3UParser::isComplete() const { 195 return mIsComplete; 196} 197 198bool M3UParser::isEvent() const { 199 return mIsEvent; 200} 201 202sp<AMessage> M3UParser::meta() { 203 return mMeta; 204} 205 206size_t M3UParser::size() { 207 return mItems.size(); 208} 209 210bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { 211 if (uri) { 212 uri->clear(); 213 } 214 215 if (meta) { 216 *meta = NULL; 217 } 218 219 if (index >= mItems.size()) { 220 return false; 221 } 222 223 if (uri) { 224 *uri = mItems.itemAt(index).mURI; 225 } 226 227 if (meta) { 228 *meta = mItems.itemAt(index).mMeta; 229 } 230 231 return true; 232} 233 234void M3UParser::pickRandomMediaItems() { 235 for (size_t i = 0; i < mMediaGroups.size(); ++i) { 236 mMediaGroups.valueAt(i)->pickRandomMediaItems(); 237 } 238} 239 240bool M3UParser::getTypeURI(size_t index, const char *key, AString *uri) const { 241 if (!mIsVariantPlaylist) { 242 *uri = mBaseURI; 243 244 // Assume media without any more specific attribute contains 245 // audio and video, but no subtitles. 246 return !strcmp("audio", key) || !strcmp("video", key); 247 } 248 249 CHECK_LT(index, mItems.size()); 250 251 sp<AMessage> meta = mItems.itemAt(index).mMeta; 252 253 AString groupID; 254 if (!meta->findString(key, &groupID)) { 255 *uri = mItems.itemAt(index).mURI; 256 257 // Assume media without any more specific attribute contains 258 // audio and video, but no subtitles. 259 return !strcmp("audio", key) || !strcmp("video", key); 260 } 261 262 sp<MediaGroup> group = mMediaGroups.valueFor(groupID); 263 if (!group->getActiveURI(uri)) { 264 return false; 265 } 266 267 if ((*uri).empty()) { 268 *uri = mItems.itemAt(index).mURI; 269 } 270 271 return true; 272} 273 274bool M3UParser::getAudioURI(size_t index, AString *uri) const { 275 return getTypeURI(index, "audio", uri); 276} 277 278bool M3UParser::getVideoURI(size_t index, AString *uri) const { 279 return getTypeURI(index, "video", uri); 280} 281 282bool M3UParser::getSubtitleURI(size_t index, AString *uri) const { 283 return getTypeURI(index, "subtitles", uri); 284} 285 286static bool MakeURL(const char *baseURL, const char *url, AString *out) { 287 out->clear(); 288 289 if (strncasecmp("http://", baseURL, 7) 290 && strncasecmp("https://", baseURL, 8) 291 && strncasecmp("file://", baseURL, 7)) { 292 // Base URL must be absolute 293 return false; 294 } 295 296 if (!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) { 297 // "url" is already an absolute URL, ignore base URL. 298 out->setTo(url); 299 300 ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 301 302 return true; 303 } 304 305 if (url[0] == '/') { 306 // URL is an absolute path. 307 308 char *protocolEnd = strstr(baseURL, "//") + 2; 309 char *pathStart = strchr(protocolEnd, '/'); 310 311 if (pathStart != NULL) { 312 out->setTo(baseURL, pathStart - baseURL); 313 } else { 314 out->setTo(baseURL); 315 } 316 317 out->append(url); 318 } else { 319 // URL is a relative path 320 321 size_t n = strlen(baseURL); 322 if (baseURL[n - 1] == '/') { 323 out->setTo(baseURL); 324 out->append(url); 325 } else { 326 const char *slashPos = strrchr(baseURL, '/'); 327 328 if (slashPos > &baseURL[6]) { 329 out->setTo(baseURL, slashPos - baseURL); 330 } else { 331 out->setTo(baseURL); 332 } 333 334 out->append("/"); 335 out->append(url); 336 } 337 } 338 339 ALOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 340 341 return true; 342} 343 344status_t M3UParser::parse(const void *_data, size_t size) { 345 int32_t lineNo = 0; 346 347 sp<AMessage> itemMeta; 348 349 const char *data = (const char *)_data; 350 size_t offset = 0; 351 uint64_t segmentRangeOffset = 0; 352 while (offset < size) { 353 size_t offsetLF = offset; 354 while (offsetLF < size && data[offsetLF] != '\n') { 355 ++offsetLF; 356 } 357 358 AString line; 359 if (offsetLF > offset && data[offsetLF - 1] == '\r') { 360 line.setTo(&data[offset], offsetLF - offset - 1); 361 } else { 362 line.setTo(&data[offset], offsetLF - offset); 363 } 364 365 // ALOGI("#%s#", line.c_str()); 366 367 if (line.empty()) { 368 offset = offsetLF + 1; 369 continue; 370 } 371 372 if (lineNo == 0 && line == "#EXTM3U") { 373 mIsExtM3U = true; 374 } 375 376 if (mIsExtM3U) { 377 status_t err = OK; 378 379 if (line.startsWith("#EXT-X-TARGETDURATION")) { 380 if (mIsVariantPlaylist) { 381 return ERROR_MALFORMED; 382 } 383 err = parseMetaData(line, &mMeta, "target-duration"); 384 } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { 385 if (mIsVariantPlaylist) { 386 return ERROR_MALFORMED; 387 } 388 err = parseMetaData(line, &mMeta, "media-sequence"); 389 } else if (line.startsWith("#EXT-X-KEY")) { 390 if (mIsVariantPlaylist) { 391 return ERROR_MALFORMED; 392 } 393 err = parseCipherInfo(line, &itemMeta, mBaseURI); 394 } else if (line.startsWith("#EXT-X-ENDLIST")) { 395 mIsComplete = true; 396 } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) { 397 mIsEvent = true; 398 } else if (line.startsWith("#EXTINF")) { 399 if (mIsVariantPlaylist) { 400 return ERROR_MALFORMED; 401 } 402 err = parseMetaDataDuration(line, &itemMeta, "durationUs"); 403 } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { 404 if (mIsVariantPlaylist) { 405 return ERROR_MALFORMED; 406 } 407 if (itemMeta == NULL) { 408 itemMeta = new AMessage; 409 } 410 itemMeta->setInt32("discontinuity", true); 411 } else if (line.startsWith("#EXT-X-STREAM-INF")) { 412 if (mMeta != NULL) { 413 return ERROR_MALFORMED; 414 } 415 mIsVariantPlaylist = true; 416 err = parseStreamInf(line, &itemMeta); 417 } else if (line.startsWith("#EXT-X-BYTERANGE")) { 418 if (mIsVariantPlaylist) { 419 return ERROR_MALFORMED; 420 } 421 422 uint64_t length, offset; 423 err = parseByteRange(line, segmentRangeOffset, &length, &offset); 424 425 if (err == OK) { 426 if (itemMeta == NULL) { 427 itemMeta = new AMessage; 428 } 429 430 itemMeta->setInt64("range-offset", offset); 431 itemMeta->setInt64("range-length", length); 432 433 segmentRangeOffset = offset + length; 434 } 435 } else if (line.startsWith("#EXT-X-MEDIA")) { 436 err = parseMedia(line); 437 } 438 439 if (err != OK) { 440 return err; 441 } 442 } 443 444 if (!line.startsWith("#")) { 445 if (!mIsVariantPlaylist) { 446 int64_t durationUs; 447 if (itemMeta == NULL 448 || !itemMeta->findInt64("durationUs", &durationUs)) { 449 return ERROR_MALFORMED; 450 } 451 } 452 453 mItems.push(); 454 Item *item = &mItems.editItemAt(mItems.size() - 1); 455 456 CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); 457 458 item->mMeta = itemMeta; 459 460 itemMeta.clear(); 461 } 462 463 offset = offsetLF + 1; 464 ++lineNo; 465 } 466 467 return OK; 468} 469 470// static 471status_t M3UParser::parseMetaData( 472 const AString &line, sp<AMessage> *meta, const char *key) { 473 ssize_t colonPos = line.find(":"); 474 475 if (colonPos < 0) { 476 return ERROR_MALFORMED; 477 } 478 479 int32_t x; 480 status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); 481 482 if (err != OK) { 483 return err; 484 } 485 486 if (meta->get() == NULL) { 487 *meta = new AMessage; 488 } 489 (*meta)->setInt32(key, x); 490 491 return OK; 492} 493 494// static 495status_t M3UParser::parseMetaDataDuration( 496 const AString &line, sp<AMessage> *meta, const char *key) { 497 ssize_t colonPos = line.find(":"); 498 499 if (colonPos < 0) { 500 return ERROR_MALFORMED; 501 } 502 503 double x; 504 status_t err = ParseDouble(line.c_str() + colonPos + 1, &x); 505 506 if (err != OK) { 507 return err; 508 } 509 510 if (meta->get() == NULL) { 511 *meta = new AMessage; 512 } 513 (*meta)->setInt64(key, (int64_t)x * 1E6); 514 515 return OK; 516} 517 518// Find the next occurence of the character "what" at or after "offset", 519// but ignore occurences between quotation marks. 520// Return the index of the occurrence or -1 if not found. 521static ssize_t FindNextUnquoted( 522 const AString &line, char what, size_t offset) { 523 CHECK_NE((int)what, (int)'"'); 524 525 bool quoted = false; 526 while (offset < line.size()) { 527 char c = line.c_str()[offset]; 528 529 if (c == '"') { 530 quoted = !quoted; 531 } else if (c == what && !quoted) { 532 return offset; 533 } 534 535 ++offset; 536 } 537 538 return -1; 539} 540 541status_t M3UParser::parseStreamInf( 542 const AString &line, sp<AMessage> *meta) const { 543 ssize_t colonPos = line.find(":"); 544 545 if (colonPos < 0) { 546 return ERROR_MALFORMED; 547 } 548 549 size_t offset = colonPos + 1; 550 551 while (offset < line.size()) { 552 ssize_t end = FindNextUnquoted(line, ',', offset); 553 if (end < 0) { 554 end = line.size(); 555 } 556 557 AString attr(line, offset, end - offset); 558 attr.trim(); 559 560 offset = end + 1; 561 562 ssize_t equalPos = attr.find("="); 563 if (equalPos < 0) { 564 continue; 565 } 566 567 AString key(attr, 0, equalPos); 568 key.trim(); 569 570 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 571 val.trim(); 572 573 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 574 575 if (!strcasecmp("bandwidth", key.c_str())) { 576 const char *s = val.c_str(); 577 char *end; 578 unsigned long x = strtoul(s, &end, 10); 579 580 if (end == s || *end != '\0') { 581 // malformed 582 continue; 583 } 584 585 if (meta->get() == NULL) { 586 *meta = new AMessage; 587 } 588 (*meta)->setInt32("bandwidth", x); 589 } else if (!strcasecmp("audio", key.c_str()) 590 || !strcasecmp("video", key.c_str()) 591 || !strcasecmp("subtitles", key.c_str())) { 592 if (val.size() < 2 593 || val.c_str()[0] != '"' 594 || val.c_str()[val.size() - 1] != '"') { 595 ALOGE("Expected quoted string for %s attribute, " 596 "got '%s' instead.", 597 key.c_str(), val.c_str()); 598 599 return ERROR_MALFORMED; 600 } 601 602 AString groupID(val, 1, val.size() - 2); 603 ssize_t groupIndex = mMediaGroups.indexOfKey(groupID); 604 605 if (groupIndex < 0) { 606 ALOGE("Undefined media group '%s' referenced in stream info.", 607 groupID.c_str()); 608 609 return ERROR_MALFORMED; 610 } 611 612 key.tolower(); 613 (*meta)->setString(key.c_str(), groupID.c_str()); 614 } 615 } 616 617 return OK; 618} 619 620// static 621status_t M3UParser::parseCipherInfo( 622 const AString &line, sp<AMessage> *meta, const AString &baseURI) { 623 ssize_t colonPos = line.find(":"); 624 625 if (colonPos < 0) { 626 return ERROR_MALFORMED; 627 } 628 629 size_t offset = colonPos + 1; 630 631 while (offset < line.size()) { 632 ssize_t end = FindNextUnquoted(line, ',', offset); 633 if (end < 0) { 634 end = line.size(); 635 } 636 637 AString attr(line, offset, end - offset); 638 attr.trim(); 639 640 offset = end + 1; 641 642 ssize_t equalPos = attr.find("="); 643 if (equalPos < 0) { 644 continue; 645 } 646 647 AString key(attr, 0, equalPos); 648 key.trim(); 649 650 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 651 val.trim(); 652 653 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 654 655 key.tolower(); 656 657 if (key == "method" || key == "uri" || key == "iv") { 658 if (meta->get() == NULL) { 659 *meta = new AMessage; 660 } 661 662 if (key == "uri") { 663 if (val.size() >= 2 664 && val.c_str()[0] == '"' 665 && val.c_str()[val.size() - 1] == '"') { 666 // Remove surrounding quotes. 667 AString tmp(val, 1, val.size() - 2); 668 val = tmp; 669 } 670 671 AString absURI; 672 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) { 673 val = absURI; 674 } else { 675 ALOGE("failed to make absolute url for '%s'.", 676 val.c_str()); 677 } 678 } 679 680 key.insert(AString("cipher-"), 0); 681 682 (*meta)->setString(key.c_str(), val.c_str(), val.size()); 683 } 684 } 685 686 return OK; 687} 688 689// static 690status_t M3UParser::parseByteRange( 691 const AString &line, uint64_t curOffset, 692 uint64_t *length, uint64_t *offset) { 693 ssize_t colonPos = line.find(":"); 694 695 if (colonPos < 0) { 696 return ERROR_MALFORMED; 697 } 698 699 ssize_t atPos = line.find("@", colonPos + 1); 700 701 AString lenStr; 702 if (atPos < 0) { 703 lenStr = AString(line, colonPos + 1, line.size() - colonPos - 1); 704 } else { 705 lenStr = AString(line, colonPos + 1, atPos - colonPos - 1); 706 } 707 708 lenStr.trim(); 709 710 const char *s = lenStr.c_str(); 711 char *end; 712 *length = strtoull(s, &end, 10); 713 714 if (s == end || *end != '\0') { 715 return ERROR_MALFORMED; 716 } 717 718 if (atPos >= 0) { 719 AString offStr = AString(line, atPos + 1, line.size() - atPos - 1); 720 offStr.trim(); 721 722 const char *s = offStr.c_str(); 723 *offset = strtoull(s, &end, 10); 724 725 if (s == end || *end != '\0') { 726 return ERROR_MALFORMED; 727 } 728 } else { 729 *offset = curOffset; 730 } 731 732 return OK; 733} 734 735status_t M3UParser::parseMedia(const AString &line) { 736 ssize_t colonPos = line.find(":"); 737 738 if (colonPos < 0) { 739 return ERROR_MALFORMED; 740 } 741 742 bool haveGroupType = false; 743 MediaGroup::Type groupType = MediaGroup::TYPE_AUDIO; 744 745 bool haveGroupID = false; 746 AString groupID; 747 748 bool haveGroupLanguage = false; 749 AString groupLanguage; 750 751 bool haveGroupName = false; 752 AString groupName; 753 754 bool haveGroupAutoselect = false; 755 bool groupAutoselect = false; 756 757 bool haveGroupDefault = false; 758 bool groupDefault = false; 759 760 bool haveGroupForced = false; 761 bool groupForced = false; 762 763 bool haveGroupURI = false; 764 AString groupURI; 765 766 size_t offset = colonPos + 1; 767 768 while (offset < line.size()) { 769 ssize_t end = FindNextUnquoted(line, ',', offset); 770 if (end < 0) { 771 end = line.size(); 772 } 773 774 AString attr(line, offset, end - offset); 775 attr.trim(); 776 777 offset = end + 1; 778 779 ssize_t equalPos = attr.find("="); 780 if (equalPos < 0) { 781 continue; 782 } 783 784 AString key(attr, 0, equalPos); 785 key.trim(); 786 787 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 788 val.trim(); 789 790 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 791 792 if (!strcasecmp("type", key.c_str())) { 793 if (!strcasecmp("subtitles", val.c_str())) { 794 groupType = MediaGroup::TYPE_SUBS; 795 } else if (!strcasecmp("audio", val.c_str())) { 796 groupType = MediaGroup::TYPE_AUDIO; 797 } else if (!strcasecmp("video", val.c_str())) { 798 groupType = MediaGroup::TYPE_VIDEO; 799 } else { 800 ALOGE("Invalid media group type '%s'", val.c_str()); 801 return ERROR_MALFORMED; 802 } 803 804 haveGroupType = true; 805 } else if (!strcasecmp("group-id", key.c_str())) { 806 if (val.size() < 2 807 || val.c_str()[0] != '"' 808 || val.c_str()[val.size() - 1] != '"') { 809 ALOGE("Expected quoted string for GROUP-ID, got '%s' instead.", 810 val.c_str()); 811 812 return ERROR_MALFORMED; 813 } 814 815 groupID.setTo(val, 1, val.size() - 2); 816 haveGroupID = true; 817 } else if (!strcasecmp("language", key.c_str())) { 818 if (val.size() < 2 819 || val.c_str()[0] != '"' 820 || val.c_str()[val.size() - 1] != '"') { 821 ALOGE("Expected quoted string for LANGUAGE, got '%s' instead.", 822 val.c_str()); 823 824 return ERROR_MALFORMED; 825 } 826 827 groupLanguage.setTo(val, 1, val.size() - 2); 828 haveGroupLanguage = true; 829 } else if (!strcasecmp("name", key.c_str())) { 830 if (val.size() < 2 831 || val.c_str()[0] != '"' 832 || val.c_str()[val.size() - 1] != '"') { 833 ALOGE("Expected quoted string for NAME, got '%s' instead.", 834 val.c_str()); 835 836 return ERROR_MALFORMED; 837 } 838 839 groupName.setTo(val, 1, val.size() - 2); 840 haveGroupName = true; 841 } else if (!strcasecmp("autoselect", key.c_str())) { 842 groupAutoselect = false; 843 if (!strcasecmp("YES", val.c_str())) { 844 groupAutoselect = true; 845 } else if (!strcasecmp("NO", val.c_str())) { 846 groupAutoselect = false; 847 } else { 848 ALOGE("Expected YES or NO for AUTOSELECT attribute, " 849 "got '%s' instead.", 850 val.c_str()); 851 852 return ERROR_MALFORMED; 853 } 854 855 haveGroupAutoselect = true; 856 } else if (!strcasecmp("default", key.c_str())) { 857 groupDefault = false; 858 if (!strcasecmp("YES", val.c_str())) { 859 groupDefault = true; 860 } else if (!strcasecmp("NO", val.c_str())) { 861 groupDefault = false; 862 } else { 863 ALOGE("Expected YES or NO for DEFAULT attribute, " 864 "got '%s' instead.", 865 val.c_str()); 866 867 return ERROR_MALFORMED; 868 } 869 870 haveGroupDefault = true; 871 } else if (!strcasecmp("forced", key.c_str())) { 872 groupForced = false; 873 if (!strcasecmp("YES", val.c_str())) { 874 groupForced = true; 875 } else if (!strcasecmp("NO", val.c_str())) { 876 groupForced = false; 877 } else { 878 ALOGE("Expected YES or NO for FORCED attribute, " 879 "got '%s' instead.", 880 val.c_str()); 881 882 return ERROR_MALFORMED; 883 } 884 885 haveGroupForced = true; 886 } else if (!strcasecmp("uri", key.c_str())) { 887 if (val.size() < 2 888 || val.c_str()[0] != '"' 889 || val.c_str()[val.size() - 1] != '"') { 890 ALOGE("Expected quoted string for URI, got '%s' instead.", 891 val.c_str()); 892 893 return ERROR_MALFORMED; 894 } 895 896 AString tmp(val, 1, val.size() - 2); 897 898 if (!MakeURL(mBaseURI.c_str(), tmp.c_str(), &groupURI)) { 899 ALOGI("Failed to make absolute URI from '%s'.", tmp.c_str()); 900 } 901 902 haveGroupURI = true; 903 } 904 } 905 906 if (!haveGroupType || !haveGroupID || !haveGroupName) { 907 ALOGE("Incomplete EXT-X-MEDIA element."); 908 return ERROR_MALFORMED; 909 } 910 911 uint32_t flags = 0; 912 if (haveGroupAutoselect && groupAutoselect) { 913 flags |= MediaGroup::FLAG_AUTOSELECT; 914 } 915 if (haveGroupDefault && groupDefault) { 916 flags |= MediaGroup::FLAG_DEFAULT; 917 } 918 if (haveGroupForced) { 919 if (groupType != MediaGroup::TYPE_SUBS) { 920 ALOGE("The FORCED attribute MUST not be present on anything " 921 "but SUBS media."); 922 923 return ERROR_MALFORMED; 924 } 925 926 if (groupForced) { 927 flags |= MediaGroup::FLAG_FORCED; 928 } 929 } 930 if (haveGroupLanguage) { 931 flags |= MediaGroup::FLAG_HAS_LANGUAGE; 932 } 933 if (haveGroupURI) { 934 flags |= MediaGroup::FLAG_HAS_URI; 935 } 936 937 ssize_t groupIndex = mMediaGroups.indexOfKey(groupID); 938 sp<MediaGroup> group; 939 940 if (groupIndex < 0) { 941 group = new MediaGroup(groupType); 942 mMediaGroups.add(groupID, group); 943 } else { 944 group = mMediaGroups.valueAt(groupIndex); 945 946 if (group->type() != groupType) { 947 ALOGE("Attempt to put media item under group of different type " 948 "(groupType = %d, item type = %d", 949 group->type(), 950 groupType); 951 952 return ERROR_MALFORMED; 953 } 954 } 955 956 return group->addMedia( 957 groupName.c_str(), 958 haveGroupURI ? groupURI.c_str() : NULL, 959 haveGroupLanguage ? groupLanguage.c_str() : NULL, 960 flags); 961} 962 963// static 964status_t M3UParser::ParseInt32(const char *s, int32_t *x) { 965 char *end; 966 long lval = strtol(s, &end, 10); 967 968 if (end == s || (*end != '\0' && *end != ',')) { 969 return ERROR_MALFORMED; 970 } 971 972 *x = (int32_t)lval; 973 974 return OK; 975} 976 977// static 978status_t M3UParser::ParseDouble(const char *s, double *x) { 979 char *end; 980 double dval = strtod(s, &end); 981 982 if (end == s || (*end != '\0' && *end != ',')) { 983 return ERROR_MALFORMED; 984 } 985 986 *x = dval; 987 988 return OK; 989} 990 991} // namespace android 992