M3UParser.cpp revision 3856b090cd04ba5dd4a59a12430ed724d5995909
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 27namespace android { 28 29M3UParser::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 39M3UParser::~M3UParser() { 40} 41 42status_t M3UParser::initCheck() const { 43 return mInitCheck; 44} 45 46bool M3UParser::isExtM3U() const { 47 return mIsExtM3U; 48} 49 50bool M3UParser::isVariantPlaylist() const { 51 return mIsVariantPlaylist; 52} 53 54bool M3UParser::isComplete() const { 55 return mIsComplete; 56} 57 58sp<AMessage> M3UParser::meta() { 59 return mMeta; 60} 61 62size_t M3UParser::size() { 63 return mItems.size(); 64} 65 66bool 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 90static 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 148status_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 while (offset < size) { 156 size_t offsetLF = offset; 157 while (offsetLF < size && data[offsetLF] != '\n') { 158 ++offsetLF; 159 } 160 if (offsetLF >= size) { 161 break; 162 } 163 164 AString line; 165 if (offsetLF > offset && data[offsetLF - 1] == '\r') { 166 line.setTo(&data[offset], offsetLF - offset - 1); 167 } else { 168 line.setTo(&data[offset], offsetLF - offset); 169 } 170 171 // LOGI("#%s#", line.c_str()); 172 173 if (line.empty()) { 174 offset = offsetLF + 1; 175 continue; 176 } 177 178 if (lineNo == 0 && line == "#EXTM3U") { 179 mIsExtM3U = true; 180 } 181 182 if (mIsExtM3U) { 183 status_t err = OK; 184 185 if (line.startsWith("#EXT-X-TARGETDURATION")) { 186 if (mIsVariantPlaylist) { 187 return ERROR_MALFORMED; 188 } 189 err = parseMetaData(line, &mMeta, "target-duration"); 190 } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { 191 if (mIsVariantPlaylist) { 192 return ERROR_MALFORMED; 193 } 194 err = parseMetaData(line, &mMeta, "media-sequence"); 195 } else if (line.startsWith("#EXT-X-KEY")) { 196 if (mIsVariantPlaylist) { 197 return ERROR_MALFORMED; 198 } 199 err = parseCipherInfo(line, &itemMeta, mBaseURI); 200 } else if (line.startsWith("#EXT-X-ENDLIST")) { 201 mIsComplete = true; 202 } else if (line.startsWith("#EXTINF")) { 203 if (mIsVariantPlaylist) { 204 return ERROR_MALFORMED; 205 } 206 err = parseMetaDataDuration(line, &itemMeta, "durationUs"); 207 } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { 208 if (mIsVariantPlaylist) { 209 return ERROR_MALFORMED; 210 } 211 if (itemMeta == NULL) { 212 itemMeta = new AMessage; 213 } 214 itemMeta->setInt32("discontinuity", true); 215 } else if (line.startsWith("#EXT-X-STREAM-INF")) { 216 if (mMeta != NULL) { 217 return ERROR_MALFORMED; 218 } 219 mIsVariantPlaylist = true; 220 err = parseStreamInf(line, &itemMeta); 221 } 222 223 if (err != OK) { 224 return err; 225 } 226 } 227 228 if (!line.startsWith("#")) { 229 if (!mIsVariantPlaylist) { 230 int64_t durationUs; 231 if (itemMeta == NULL 232 || !itemMeta->findInt64("durationUs", &durationUs)) { 233 return ERROR_MALFORMED; 234 } 235 } 236 237 mItems.push(); 238 Item *item = &mItems.editItemAt(mItems.size() - 1); 239 240 CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); 241 242 item->mMeta = itemMeta; 243 244 itemMeta.clear(); 245 } 246 247 offset = offsetLF + 1; 248 ++lineNo; 249 } 250 251 return OK; 252} 253 254// static 255status_t M3UParser::parseMetaData( 256 const AString &line, sp<AMessage> *meta, const char *key) { 257 ssize_t colonPos = line.find(":"); 258 259 if (colonPos < 0) { 260 return ERROR_MALFORMED; 261 } 262 263 int32_t x; 264 status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); 265 266 if (err != OK) { 267 return err; 268 } 269 270 if (meta->get() == NULL) { 271 *meta = new AMessage; 272 } 273 (*meta)->setInt32(key, x); 274 275 return OK; 276} 277 278// static 279status_t M3UParser::parseMetaDataDuration( 280 const AString &line, sp<AMessage> *meta, const char *key) { 281 ssize_t colonPos = line.find(":"); 282 283 if (colonPos < 0) { 284 return ERROR_MALFORMED; 285 } 286 287 double x; 288 status_t err = ParseDouble(line.c_str() + colonPos + 1, &x); 289 290 if (err != OK) { 291 return err; 292 } 293 294 if (meta->get() == NULL) { 295 *meta = new AMessage; 296 } 297 (*meta)->setInt64(key, (int64_t)x * 1E6); 298 299 return OK; 300} 301 302// static 303status_t M3UParser::parseStreamInf( 304 const AString &line, sp<AMessage> *meta) { 305 ssize_t colonPos = line.find(":"); 306 307 if (colonPos < 0) { 308 return ERROR_MALFORMED; 309 } 310 311 size_t offset = colonPos + 1; 312 313 while (offset < line.size()) { 314 ssize_t end = line.find(",", offset); 315 if (end < 0) { 316 end = line.size(); 317 } 318 319 AString attr(line, offset, end - offset); 320 attr.trim(); 321 322 offset = end + 1; 323 324 ssize_t equalPos = attr.find("="); 325 if (equalPos < 0) { 326 continue; 327 } 328 329 AString key(attr, 0, equalPos); 330 key.trim(); 331 332 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 333 val.trim(); 334 335 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 336 337 if (!strcasecmp("bandwidth", key.c_str())) { 338 const char *s = val.c_str(); 339 char *end; 340 unsigned long x = strtoul(s, &end, 10); 341 342 if (end == s || *end != '\0') { 343 // malformed 344 continue; 345 } 346 347 if (meta->get() == NULL) { 348 *meta = new AMessage; 349 } 350 (*meta)->setInt32("bandwidth", x); 351 } 352 } 353 354 return OK; 355} 356 357// Find the next occurence of the character "what" at or after "offset", 358// but ignore occurences between quotation marks. 359// Return the index of the occurrence or -1 if not found. 360static ssize_t FindNextUnquoted( 361 const AString &line, char what, size_t offset) { 362 CHECK_NE((int)what, (int)'"'); 363 364 bool quoted = false; 365 while (offset < line.size()) { 366 char c = line.c_str()[offset]; 367 368 if (c == '"') { 369 quoted = !quoted; 370 } else if (c == what && !quoted) { 371 return offset; 372 } 373 374 ++offset; 375 } 376 377 return -1; 378} 379 380// static 381status_t M3UParser::parseCipherInfo( 382 const AString &line, sp<AMessage> *meta, const AString &baseURI) { 383 ssize_t colonPos = line.find(":"); 384 385 if (colonPos < 0) { 386 return ERROR_MALFORMED; 387 } 388 389 size_t offset = colonPos + 1; 390 391 while (offset < line.size()) { 392 ssize_t end = FindNextUnquoted(line, ',', offset); 393 if (end < 0) { 394 end = line.size(); 395 } 396 397 AString attr(line, offset, end - offset); 398 attr.trim(); 399 400 offset = end + 1; 401 402 ssize_t equalPos = attr.find("="); 403 if (equalPos < 0) { 404 continue; 405 } 406 407 AString key(attr, 0, equalPos); 408 key.trim(); 409 410 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 411 val.trim(); 412 413 ALOGV("key=%s value=%s", key.c_str(), val.c_str()); 414 415 key.tolower(); 416 417 if (key == "method" || key == "uri" || key == "iv") { 418 if (meta->get() == NULL) { 419 *meta = new AMessage; 420 } 421 422 if (key == "uri") { 423 if (val.size() >= 2 424 && val.c_str()[0] == '"' 425 && val.c_str()[val.size() - 1] == '"') { 426 // Remove surrounding quotes. 427 AString tmp(val, 1, val.size() - 2); 428 val = tmp; 429 } 430 431 AString absURI; 432 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) { 433 val = absURI; 434 } else { 435 LOGE("failed to make absolute url for '%s'.", 436 val.c_str()); 437 } 438 } 439 440 key.insert(AString("cipher-"), 0); 441 442 (*meta)->setString(key.c_str(), val.c_str(), val.size()); 443 } 444 } 445 446 return OK; 447} 448 449// static 450status_t M3UParser::ParseInt32(const char *s, int32_t *x) { 451 char *end; 452 long lval = strtol(s, &end, 10); 453 454 if (end == s || (*end != '\0' && *end != ',')) { 455 return ERROR_MALFORMED; 456 } 457 458 *x = (int32_t)lval; 459 460 return OK; 461} 462 463// static 464status_t M3UParser::ParseDouble(const char *s, double *x) { 465 char *end; 466 double dval = strtod(s, &end); 467 468 if (end == s || (*end != '\0' && *end != ',')) { 469 return ERROR_MALFORMED; 470 } 471 472 *x = dval; 473 474 return OK; 475} 476 477} // namespace android 478