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