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