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