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