Loading media/libstagefright/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_vpxdec \ libvpx \ libstagefright_mpeg2ts \ libstagefright_httplive \ LOCAL_SHARED_LIBRARIES += \ libstagefright_amrnb_common \ Loading media/libstagefright/AwesomePlayer.cpp +31 −4 Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ #include <surfaceflinger/ISurface.h> #include "include/LiveSource.h" namespace android { struct AwesomeEvent : public TimedEventQueue::Event { Loading Loading @@ -261,6 +263,16 @@ status_t AwesomePlayer::setDataSource_l( status_t AwesomePlayer::setDataSource( int fd, int64_t offset, int64_t length) { #if 0 // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_96.m3u8"); // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_1500.m3u8"); return setDataSource("httplive://iphone.video.hsn.com/iPhone_high.m3u8"); // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/iphonewebcast/webcast090209_all/webcast090209_all.m3u8"); // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_062209_iphone/hi/prog_index.m3u8"); // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_googmaps/hi/prog_index.m3u8"); // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/mtv/ni_spo_25a_rt74137_clip_syn/hi/prog_index.m3u8"); #endif Mutex::Autolock autoLock(mLock); reset_l(); Loading Loading @@ -438,11 +450,11 @@ void AwesomePlayer::onBufferingUpdate() { durationUs = mDurationUs; } if (durationUs >= 0) { int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs(); LOGV("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6); LOGI("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6); if (durationUs >= 0) { int64_t positionUs; getPosition(&positionUs); Loading @@ -453,7 +465,8 @@ void AwesomePlayer::onBufferingUpdate() { postBufferingEvent_l(); } else { LOGE("Not sending buffering status because duration is unknown."); // LOGE("Not sending buffering status because duration is unknown."); postBufferingEvent_l(); } } Loading Loading @@ -1123,6 +1136,20 @@ status_t AwesomePlayer::finishSetDataSource_l() { mConnectingDataSource, 64 * 1024, 10); mConnectingDataSource.clear(); } else if (!strncasecmp(mUri.string(), "httplive://", 11)) { String8 uri("http://"); uri.append(mUri.string() + 11); dataSource = new LiveSource(uri.string()); if (dataSource->flags() & DataSource::kWantsPrefetching) { mPrefetcher = new Prefetcher; } sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); return setDataSource_l(extractor); } else { dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders); } Loading media/libstagefright/httplive/Android.mk 0 → 100644 +20 −0 Original line number Diff line number Diff line LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ LiveSource.cpp \ M3UParser.cpp \ LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ $(TOP)/frameworks/base/media/libstagefright LOCAL_MODULE:= libstagefright_httplive ifeq ($(TARGET_ARCH),arm) LOCAL_CFLAGS += -Wno-psabi endif include $(BUILD_STATIC_LIBRARY) media/libstagefright/httplive/LiveSource.cpp 0 → 100644 +191 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "LiveSource" #include <utils/Log.h> #include "include/LiveSource.h" #include "include/HTTPStream.h" #include "include/M3UParser.h" #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/HTTPDataSource.h> #include <media/stagefright/MediaDebug.h> namespace android { LiveSource::LiveSource(const char *url) : mURL(url), mInitCheck(NO_INIT), mPlaylistIndex(0), mLastFetchTimeUs(-1), mSourceSize(0), mOffsetBias(0) { if (switchToNext()) { mInitCheck = OK; } } LiveSource::~LiveSource() { } status_t LiveSource::initCheck() const { return mInitCheck; } bool LiveSource::loadPlaylist() { mPlaylist.clear(); mPlaylistIndex = 0; sp<ABuffer> buffer; status_t err = fetchM3U(mURL.c_str(), &buffer); if (err != OK) { return false; } mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size()); if (mPlaylist->initCheck() != OK) { return false; } if (!mPlaylist->meta()->findInt32( "media-sequence", &mFirstItemSequenceNumber)) { mFirstItemSequenceNumber = 0; } return true; } static int64_t getNowUs() { struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll; } bool LiveSource::switchToNext() { mOffsetBias += mSourceSize; mSourceSize = 0; if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll || mPlaylistIndex == mPlaylist->size()) { int32_t nextSequenceNumber = mPlaylistIndex + mFirstItemSequenceNumber; if (!loadPlaylist()) { LOGE("failed to reload playlist"); return false; } if (mLastFetchTimeUs < 0) { mPlaylistIndex = mPlaylist->size() / 2; } else { if (nextSequenceNumber < mFirstItemSequenceNumber || nextSequenceNumber >= mFirstItemSequenceNumber + (int32_t)mPlaylist->size()) { LOGE("Cannot find sequence number %d in new playlist", nextSequenceNumber); return false; } mPlaylistIndex = nextSequenceNumber - mFirstItemSequenceNumber; } mLastFetchTimeUs = getNowUs(); } AString uri; CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri)); LOGI("switching to %s", uri.c_str()); mSource = new HTTPDataSource(uri.c_str()); if (mSource->connect() != OK || mSource->getSize(&mSourceSize) != OK) { return false; } mPlaylistIndex++; return true; } ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) { CHECK(offset >= mOffsetBias); offset -= mOffsetBias; if (offset >= mSourceSize) { CHECK_EQ(offset, mSourceSize); offset -= mSourceSize; if (!switchToNext()) { return ERROR_END_OF_STREAM; } } size_t numRead = 0; while (numRead < size) { ssize_t n = mSource->readAt( offset + numRead, (uint8_t *)data + numRead, size - numRead); if (n <= 0) { break; } numRead += n; } return numRead; } status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) { *out = NULL; mSource = new HTTPDataSource(url); status_t err = mSource->connect(); if (err != OK) { return err; } off_t size; err = mSource->getSize(&size); if (err != OK) { return err; } sp<ABuffer> buffer = new ABuffer(size); size_t offset = 0; while (offset < (size_t)size) { ssize_t n = mSource->readAt( offset, buffer->data() + offset, size - offset); if (n <= 0) { return ERROR_IO; } offset += n; } *out = buffer; return OK; } } // namespace android media/libstagefright/httplive/M3UParser.cpp 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "include/M3UParser.h" #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaErrors.h> namespace android { M3UParser::M3UParser( const char *baseURI, const void *data, size_t size) : mInitCheck(NO_INIT), mBaseURI(baseURI), mIsExtM3U(false), mIsVariantPlaylist(false) { mInitCheck = parse(data, size); } M3UParser::~M3UParser() { } status_t M3UParser::initCheck() const { return mInitCheck; } bool M3UParser::isExtM3U() const { return mIsExtM3U; } bool M3UParser::isVariantPlaylist() const { return mIsVariantPlaylist; } sp<AMessage> M3UParser::meta() { return mMeta; } size_t M3UParser::size() { return mItems.size(); } bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { uri->clear(); if (meta) { *meta = NULL; } if (index >= mItems.size()) { return false; } *uri = mItems.itemAt(index).mURI; if (meta) { *meta = mItems.itemAt(index).mMeta; } return true; } static bool MakeURL(const char *baseURL, const char *url, AString *out) { out->clear(); if (strncasecmp("http://", baseURL, 7)) { // Base URL must be absolute return false; } if (!strncasecmp("http://", url, 7)) { // "url" is already an absolute URL, ignore base URL. out->setTo(url); return true; } size_t n = strlen(baseURL); if (baseURL[n - 1] == '/') { out->setTo(baseURL); out->append(url); } else { char *slashPos = strrchr(baseURL, '/'); if (slashPos > &baseURL[6]) { out->setTo(baseURL, slashPos - baseURL); } else { out->setTo(baseURL); } out->append("/"); out->append(url); } return true; } status_t M3UParser::parse(const void *_data, size_t size) { int32_t lineNo = 0; sp<AMessage> itemMeta; const char *data = (const char *)_data; size_t offset = 0; while (offset < size) { size_t offsetLF = offset; while (offsetLF < size && data[offsetLF] != '\n') { ++offsetLF; } if (offsetLF >= size) { break; } AString line; if (offsetLF > offset && data[offsetLF - 1] == '\r') { line.setTo(&data[offset], offsetLF - offset - 1); } else { line.setTo(&data[offset], offsetLF - offset); } LOGI("#%s#", line.c_str()); if (lineNo == 0 && line == "#EXTM3U") { mIsExtM3U = true; } if (mIsExtM3U) { status_t err = OK; if (line.startsWith("#EXT-X-TARGETDURATION")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &mMeta, "target-duration"); } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &mMeta, "media-sequence"); } else if (line.startsWith("#EXTINF")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &itemMeta, "duration"); } else if (line.startsWith("#EXT-X-STREAM-INF")) { if (mMeta != NULL) { return ERROR_MALFORMED; } mIsVariantPlaylist = true; } if (err != OK) { return err; } } if (!line.startsWith("#")) { if (!mIsVariantPlaylist) { int32_t durationSecs; if (itemMeta == NULL || !itemMeta->findInt32("duration", &durationSecs)) { return ERROR_MALFORMED; } } mItems.push(); Item *item = &mItems.editItemAt(mItems.size() - 1); CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); item->mMeta = itemMeta; itemMeta.clear(); } offset = offsetLF + 1; ++lineNo; } return OK; } // static status_t M3UParser::parseMetaData( const AString &line, sp<AMessage> *meta, const char *key) { ssize_t colonPos = line.find(":"); if (colonPos < 0) { return ERROR_MALFORMED; } int32_t x; status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); if (err != OK) { return err; } if (meta->get() == NULL) { *meta = new AMessage; } (*meta)->setInt32(key, x); return OK; } // static status_t M3UParser::ParseInt32(const char *s, int32_t *x) { char *end; long lval = strtol(s, &end, 10); if (end == s || (*end != '\0' && *end != ',')) { return ERROR_MALFORMED; } *x = (int32_t)lval; return OK; } } // namespace android Loading
media/libstagefright/Android.mk +1 −0 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ LOCAL_STATIC_LIBRARIES := \ libstagefright_vpxdec \ libvpx \ libstagefright_mpeg2ts \ libstagefright_httplive \ LOCAL_SHARED_LIBRARIES += \ libstagefright_amrnb_common \ Loading
media/libstagefright/AwesomePlayer.cpp +31 −4 Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ #include <surfaceflinger/ISurface.h> #include "include/LiveSource.h" namespace android { struct AwesomeEvent : public TimedEventQueue::Event { Loading Loading @@ -261,6 +263,16 @@ status_t AwesomePlayer::setDataSource_l( status_t AwesomePlayer::setDataSource( int fd, int64_t offset, int64_t length) { #if 0 // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_96.m3u8"); // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/nasatv/nasatv_1500.m3u8"); return setDataSource("httplive://iphone.video.hsn.com/iPhone_high.m3u8"); // return setDataSource("httplive://iphoned5.akamai.com.edgesuite.net/mhbarron/iphonewebcast/webcast090209_all/webcast090209_all.m3u8"); // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_062209_iphone/hi/prog_index.m3u8"); // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/usat/tt_googmaps/hi/prog_index.m3u8"); // return setDataSource("httplive://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/mtv/ni_spo_25a_rt74137_clip_syn/hi/prog_index.m3u8"); #endif Mutex::Autolock autoLock(mLock); reset_l(); Loading Loading @@ -438,11 +450,11 @@ void AwesomePlayer::onBufferingUpdate() { durationUs = mDurationUs; } if (durationUs >= 0) { int64_t cachedDurationUs = mPrefetcher->getCachedDurationUs(); LOGV("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6); LOGI("cache holds %.2f secs worth of data.", cachedDurationUs / 1E6); if (durationUs >= 0) { int64_t positionUs; getPosition(&positionUs); Loading @@ -453,7 +465,8 @@ void AwesomePlayer::onBufferingUpdate() { postBufferingEvent_l(); } else { LOGE("Not sending buffering status because duration is unknown."); // LOGE("Not sending buffering status because duration is unknown."); postBufferingEvent_l(); } } Loading Loading @@ -1123,6 +1136,20 @@ status_t AwesomePlayer::finishSetDataSource_l() { mConnectingDataSource, 64 * 1024, 10); mConnectingDataSource.clear(); } else if (!strncasecmp(mUri.string(), "httplive://", 11)) { String8 uri("http://"); uri.append(mUri.string() + 11); dataSource = new LiveSource(uri.string()); if (dataSource->flags() & DataSource::kWantsPrefetching) { mPrefetcher = new Prefetcher; } sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); return setDataSource_l(extractor); } else { dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders); } Loading
media/libstagefright/httplive/Android.mk 0 → 100644 +20 −0 Original line number Diff line number Diff line LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ LiveSource.cpp \ M3UParser.cpp \ LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \ $(TOP)/frameworks/base/media/libstagefright LOCAL_MODULE:= libstagefright_httplive ifeq ($(TARGET_ARCH),arm) LOCAL_CFLAGS += -Wno-psabi endif include $(BUILD_STATIC_LIBRARY)
media/libstagefright/httplive/LiveSource.cpp 0 → 100644 +191 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "LiveSource" #include <utils/Log.h> #include "include/LiveSource.h" #include "include/HTTPStream.h" #include "include/M3UParser.h" #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/HTTPDataSource.h> #include <media/stagefright/MediaDebug.h> namespace android { LiveSource::LiveSource(const char *url) : mURL(url), mInitCheck(NO_INIT), mPlaylistIndex(0), mLastFetchTimeUs(-1), mSourceSize(0), mOffsetBias(0) { if (switchToNext()) { mInitCheck = OK; } } LiveSource::~LiveSource() { } status_t LiveSource::initCheck() const { return mInitCheck; } bool LiveSource::loadPlaylist() { mPlaylist.clear(); mPlaylistIndex = 0; sp<ABuffer> buffer; status_t err = fetchM3U(mURL.c_str(), &buffer); if (err != OK) { return false; } mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size()); if (mPlaylist->initCheck() != OK) { return false; } if (!mPlaylist->meta()->findInt32( "media-sequence", &mFirstItemSequenceNumber)) { mFirstItemSequenceNumber = 0; } return true; } static int64_t getNowUs() { struct timeval tv; gettimeofday(&tv, NULL); return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll; } bool LiveSource::switchToNext() { mOffsetBias += mSourceSize; mSourceSize = 0; if (mLastFetchTimeUs < 0 || getNowUs() >= mLastFetchTimeUs + 15000000ll || mPlaylistIndex == mPlaylist->size()) { int32_t nextSequenceNumber = mPlaylistIndex + mFirstItemSequenceNumber; if (!loadPlaylist()) { LOGE("failed to reload playlist"); return false; } if (mLastFetchTimeUs < 0) { mPlaylistIndex = mPlaylist->size() / 2; } else { if (nextSequenceNumber < mFirstItemSequenceNumber || nextSequenceNumber >= mFirstItemSequenceNumber + (int32_t)mPlaylist->size()) { LOGE("Cannot find sequence number %d in new playlist", nextSequenceNumber); return false; } mPlaylistIndex = nextSequenceNumber - mFirstItemSequenceNumber; } mLastFetchTimeUs = getNowUs(); } AString uri; CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri)); LOGI("switching to %s", uri.c_str()); mSource = new HTTPDataSource(uri.c_str()); if (mSource->connect() != OK || mSource->getSize(&mSourceSize) != OK) { return false; } mPlaylistIndex++; return true; } ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) { CHECK(offset >= mOffsetBias); offset -= mOffsetBias; if (offset >= mSourceSize) { CHECK_EQ(offset, mSourceSize); offset -= mSourceSize; if (!switchToNext()) { return ERROR_END_OF_STREAM; } } size_t numRead = 0; while (numRead < size) { ssize_t n = mSource->readAt( offset + numRead, (uint8_t *)data + numRead, size - numRead); if (n <= 0) { break; } numRead += n; } return numRead; } status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) { *out = NULL; mSource = new HTTPDataSource(url); status_t err = mSource->connect(); if (err != OK) { return err; } off_t size; err = mSource->getSize(&size); if (err != OK) { return err; } sp<ABuffer> buffer = new ABuffer(size); size_t offset = 0; while (offset < (size_t)size) { ssize_t n = mSource->readAt( offset, buffer->data() + offset, size - offset); if (n <= 0) { return ERROR_IO; } offset += n; } *out = buffer; return OK; } } // namespace android
media/libstagefright/httplive/M3UParser.cpp 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "include/M3UParser.h" #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaErrors.h> namespace android { M3UParser::M3UParser( const char *baseURI, const void *data, size_t size) : mInitCheck(NO_INIT), mBaseURI(baseURI), mIsExtM3U(false), mIsVariantPlaylist(false) { mInitCheck = parse(data, size); } M3UParser::~M3UParser() { } status_t M3UParser::initCheck() const { return mInitCheck; } bool M3UParser::isExtM3U() const { return mIsExtM3U; } bool M3UParser::isVariantPlaylist() const { return mIsVariantPlaylist; } sp<AMessage> M3UParser::meta() { return mMeta; } size_t M3UParser::size() { return mItems.size(); } bool M3UParser::itemAt(size_t index, AString *uri, sp<AMessage> *meta) { uri->clear(); if (meta) { *meta = NULL; } if (index >= mItems.size()) { return false; } *uri = mItems.itemAt(index).mURI; if (meta) { *meta = mItems.itemAt(index).mMeta; } return true; } static bool MakeURL(const char *baseURL, const char *url, AString *out) { out->clear(); if (strncasecmp("http://", baseURL, 7)) { // Base URL must be absolute return false; } if (!strncasecmp("http://", url, 7)) { // "url" is already an absolute URL, ignore base URL. out->setTo(url); return true; } size_t n = strlen(baseURL); if (baseURL[n - 1] == '/') { out->setTo(baseURL); out->append(url); } else { char *slashPos = strrchr(baseURL, '/'); if (slashPos > &baseURL[6]) { out->setTo(baseURL, slashPos - baseURL); } else { out->setTo(baseURL); } out->append("/"); out->append(url); } return true; } status_t M3UParser::parse(const void *_data, size_t size) { int32_t lineNo = 0; sp<AMessage> itemMeta; const char *data = (const char *)_data; size_t offset = 0; while (offset < size) { size_t offsetLF = offset; while (offsetLF < size && data[offsetLF] != '\n') { ++offsetLF; } if (offsetLF >= size) { break; } AString line; if (offsetLF > offset && data[offsetLF - 1] == '\r') { line.setTo(&data[offset], offsetLF - offset - 1); } else { line.setTo(&data[offset], offsetLF - offset); } LOGI("#%s#", line.c_str()); if (lineNo == 0 && line == "#EXTM3U") { mIsExtM3U = true; } if (mIsExtM3U) { status_t err = OK; if (line.startsWith("#EXT-X-TARGETDURATION")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &mMeta, "target-duration"); } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &mMeta, "media-sequence"); } else if (line.startsWith("#EXTINF")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } err = parseMetaData(line, &itemMeta, "duration"); } else if (line.startsWith("#EXT-X-STREAM-INF")) { if (mMeta != NULL) { return ERROR_MALFORMED; } mIsVariantPlaylist = true; } if (err != OK) { return err; } } if (!line.startsWith("#")) { if (!mIsVariantPlaylist) { int32_t durationSecs; if (itemMeta == NULL || !itemMeta->findInt32("duration", &durationSecs)) { return ERROR_MALFORMED; } } mItems.push(); Item *item = &mItems.editItemAt(mItems.size() - 1); CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI)); item->mMeta = itemMeta; itemMeta.clear(); } offset = offsetLF + 1; ++lineNo; } return OK; } // static status_t M3UParser::parseMetaData( const AString &line, sp<AMessage> *meta, const char *key) { ssize_t colonPos = line.find(":"); if (colonPos < 0) { return ERROR_MALFORMED; } int32_t x; status_t err = ParseInt32(line.c_str() + colonPos + 1, &x); if (err != OK) { return err; } if (meta->get() == NULL) { *meta = new AMessage; } (*meta)->setInt32(key, x); return OK; } // static status_t M3UParser::ParseInt32(const char *s, int32_t *x) { char *end; long lval = strtol(s, &end, 10); if (end == s || (*end != '\0' && *end != ',')) { return ERROR_MALFORMED; } *x = (int32_t)lval; return OK; } } // namespace android