M3UParser.cpp revision be2bda4abed36eb6adf474c317d1492b4aec8f1a
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
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      mIsEvent(false) {
37    mInitCheck = parse(data, size);
38}
39
40M3UParser::~M3UParser() {
41}
42
43status_t M3UParser::initCheck() const {
44    return mInitCheck;
45}
46
47bool M3UParser::isExtM3U() const {
48    return mIsExtM3U;
49}
50
51bool M3UParser::isVariantPlaylist() const {
52    return mIsVariantPlaylist;
53}
54
55bool M3UParser::isComplete() const {
56    return mIsComplete;
57}
58
59bool M3UParser::isEvent() const {
60    return mIsEvent;
61}
62
63sp<AMessage> M3UParser::meta() {
64    return mMeta;
65}
66
67size_t M3UParser::size() {
68    return mItems.size();
69}
70
71bool 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
95static 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
153status_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
278status_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
302status_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
326status_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.
383static 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
404status_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
473status_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
519status_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
533status_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