M3UParser.cpp revision df42f949c8bd05b81d94633767514fff88f52062
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/AMessage.h> 24#include <media/stagefright/MediaDebug.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 uri->clear(); 68 if (meta) { *meta = NULL; } 69 70 if (index >= mItems.size()) { 71 return false; 72 } 73 74 *uri = mItems.itemAt(index).mURI; 75 76 if (meta) { 77 *meta = mItems.itemAt(index).mMeta; 78 } 79 80 return true; 81} 82 83static bool MakeURL(const char *baseURL, const char *url, AString *out) { 84 out->clear(); 85 86 if (strncasecmp("http://", baseURL, 7) 87 && strncasecmp("https://", baseURL, 8) 88 && strncasecmp("file://", baseURL, 7)) { 89 // Base URL must be absolute 90 return false; 91 } 92 93 if (!strncasecmp("http://", url, 7) || !strncasecmp("https://", url, 8)) { 94 // "url" is already an absolute URL, ignore base URL. 95 out->setTo(url); 96 97 LOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 98 99 return true; 100 } 101 102 size_t n = strlen(baseURL); 103 if (baseURL[n - 1] == '/') { 104 out->setTo(baseURL); 105 out->append(url); 106 } else { 107 const char *slashPos = strrchr(baseURL, '/'); 108 109 if (slashPos > &baseURL[6]) { 110 out->setTo(baseURL, slashPos - baseURL); 111 } else { 112 out->setTo(baseURL); 113 } 114 115 out->append("/"); 116 out->append(url); 117 } 118 119 LOGV("base:'%s', url:'%s' => '%s'", baseURL, url, out->c_str()); 120 121 return true; 122} 123 124status_t M3UParser::parse(const void *_data, size_t size) { 125 int32_t lineNo = 0; 126 127 sp<AMessage> itemMeta; 128 129 const char *data = (const char *)_data; 130 size_t offset = 0; 131 while (offset < size) { 132 size_t offsetLF = offset; 133 while (offsetLF < size && data[offsetLF] != '\n') { 134 ++offsetLF; 135 } 136 if (offsetLF >= size) { 137 break; 138 } 139 140 AString line; 141 if (offsetLF > offset && data[offsetLF - 1] == '\r') { 142 line.setTo(&data[offset], offsetLF - offset - 1); 143 } else { 144 line.setTo(&data[offset], offsetLF - offset); 145 } 146 147 // LOGI("#%s#", line.c_str()); 148 149 if (line.empty()) { 150 offset = offsetLF + 1; 151 continue; 152 } 153 154 if (lineNo == 0 && line == "#EXTM3U") { 155 mIsExtM3U = true; 156 } 157 158 if (mIsExtM3U) { 159 status_t err = OK; 160 161 if (line.startsWith("#EXT-X-TARGETDURATION")) { 162 if (mIsVariantPlaylist) { 163 return ERROR_MALFORMED; 164 } 165 err = parseMetaData(line, &mMeta, "target-duration"); 166 } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { 167 if (mIsVariantPlaylist) { 168 return ERROR_MALFORMED; 169 } 170 err = parseMetaData(line, &mMeta, "media-sequence"); 171 } else if (line.startsWith("#EXT-X-KEY")) { 172 if (mIsVariantPlaylist) { 173 return ERROR_MALFORMED; 174 } 175 err = parseCipherInfo(line, &itemMeta, mBaseURI); 176 } else if (line.startsWith("#EXT-X-ENDLIST")) { 177 mIsComplete = true; 178 } else if (line.startsWith("#EXTINF")) { 179 if (mIsVariantPlaylist) { 180 return ERROR_MALFORMED; 181 } 182 err = parseMetaData(line, &itemMeta, "duration"); 183 } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { 184 if (mIsVariantPlaylist) { 185 return ERROR_MALFORMED; 186 } 187 if (itemMeta == NULL) { 188 itemMeta = new AMessage; 189 } 190 itemMeta->setInt32("discontinuity", true); 191 } else if (line.startsWith("#EXT-X-STREAM-INF")) { 192 if (mMeta != NULL) { 193 return ERROR_MALFORMED; 194 } 195 mIsVariantPlaylist = true; 196 err = parseStreamInf(line, &itemMeta); 197 } 198 199 if (err != OK) { 200 return err; 201 } 202 } 203 204 if (!line.startsWith("#")) { 205 if (!mIsVariantPlaylist) { 206 int32_t durationSecs; 207 if (itemMeta == NULL 208 || !itemMeta->findInt32("duration", &durationSecs)) { 209 return ERROR_MALFORMED; 210 } 211 } 212 213 mItems.push(); 214 Item *item = &mItems.editItemAt(mItems.size() - 1); 215 216 CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); 217 218 item->mMeta = itemMeta; 219 220 itemMeta.clear(); 221 } 222 223 offset = offsetLF + 1; 224 ++lineNo; 225 } 226 227 return OK; 228} 229 230// static 231status_t M3UParser::parseMetaData( 232 const AString &line, sp<AMessage> *meta, const char *key) { 233 ssize_t colonPos = line.find(":"); 234 235 if (colonPos < 0) { 236 return ERROR_MALFORMED; 237 } 238 239 int32_t x; 240 status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); 241 242 if (err != OK) { 243 return err; 244 } 245 246 if (meta->get() == NULL) { 247 *meta = new AMessage; 248 } 249 (*meta)->setInt32(key, x); 250 251 return OK; 252} 253 254// static 255status_t M3UParser::parseStreamInf( 256 const AString &line, sp<AMessage> *meta) { 257 ssize_t colonPos = line.find(":"); 258 259 if (colonPos < 0) { 260 return ERROR_MALFORMED; 261 } 262 263 size_t offset = colonPos + 1; 264 265 while (offset < line.size()) { 266 ssize_t end = line.find(",", offset); 267 if (end < 0) { 268 end = line.size(); 269 } 270 271 AString attr(line, offset, end - offset); 272 attr.trim(); 273 274 offset = end + 1; 275 276 ssize_t equalPos = attr.find("="); 277 if (equalPos < 0) { 278 continue; 279 } 280 281 AString key(attr, 0, equalPos); 282 key.trim(); 283 284 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 285 val.trim(); 286 287 LOGV("key=%s value=%s", key.c_str(), val.c_str()); 288 289 if (!strcasecmp("bandwidth", key.c_str())) { 290 const char *s = val.c_str(); 291 char *end; 292 unsigned long x = strtoul(s, &end, 10); 293 294 if (end == s || *end != '\0') { 295 // malformed 296 continue; 297 } 298 299 if (meta->get() == NULL) { 300 *meta = new AMessage; 301 } 302 (*meta)->setInt32("bandwidth", x); 303 } 304 } 305 306 return OK; 307} 308 309// static 310status_t M3UParser::parseCipherInfo( 311 const AString &line, sp<AMessage> *meta, const AString &baseURI) { 312 ssize_t colonPos = line.find(":"); 313 314 if (colonPos < 0) { 315 return ERROR_MALFORMED; 316 } 317 318 size_t offset = colonPos + 1; 319 320 while (offset < line.size()) { 321 ssize_t end = line.find(",", offset); 322 if (end < 0) { 323 end = line.size(); 324 } 325 326 AString attr(line, offset, end - offset); 327 attr.trim(); 328 329 offset = end + 1; 330 331 ssize_t equalPos = attr.find("="); 332 if (equalPos < 0) { 333 continue; 334 } 335 336 AString key(attr, 0, equalPos); 337 key.trim(); 338 339 AString val(attr, equalPos + 1, attr.size() - equalPos - 1); 340 val.trim(); 341 342 LOGV("key=%s value=%s", key.c_str(), val.c_str()); 343 344 key.tolower(); 345 346 if (key == "method" || key == "uri" || key == "iv") { 347 if (meta->get() == NULL) { 348 *meta = new AMessage; 349 } 350 351 if (key == "uri") { 352 if (val.size() >= 2 353 && val.c_str()[0] == '"' 354 && val.c_str()[val.size() - 1] == '"') { 355 // Remove surrounding quotes. 356 AString tmp(val, 1, val.size() - 2); 357 val = tmp; 358 } 359 360 AString absURI; 361 if (MakeURL(baseURI.c_str(), val.c_str(), &absURI)) { 362 val = absURI; 363 } else { 364 LOGE("failed to make absolute url for '%s'.", 365 val.c_str()); 366 } 367 } 368 369 key.insert(AString("cipher-"), 0); 370 371 (*meta)->setString(key.c_str(), val.c_str(), val.size()); 372 } 373 } 374 375 return OK; 376} 377 378// static 379status_t M3UParser::ParseInt32(const char *s, int32_t *x) { 380 char *end; 381 long lval = strtol(s, &end, 10); 382 383 if (end == s || (*end != '\0' && *end != ',')) { 384 return ERROR_MALFORMED; 385 } 386 387 *x = (int32_t)lval; 388 389 return OK; 390} 391 392} // namespace android 393