blob: 001afc4213b367b8cf593ada3f8e0dbfc23dea83 [file] [log] [blame]
/*
* 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/M3UParser.h"
#include "include/NuHTTPDataSource.h"
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/MediaDebug.h>
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<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());
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<ABuffer> *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<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