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 "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