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