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