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