/* * 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 #include "include/LiveSource.h" #include "include/M3UParser.h" #include "include/NuHTTPDataSource.h" #include #include namespace android { LiveSource::LiveSource(const char *url) : mURL(url), mInitCheck(NO_INIT), mPlaylistIndex(0), mLastFetchTimeUs(-1), mSource(new NuHTTPDataSource), 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 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()); if (mSource->connect(uri.c_str()) != 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 *out) { *out = NULL; status_t err = mSource->connect(url); if (err != OK) { return err; } off_t size; err = mSource->getSize(&size); if (err != OK) { return err; } sp 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