From 163c493b50bb8473319a942c8feb4528cdc56c11 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 10 Jun 2010 11:08:43 -0700 Subject: Initial check in of a new http data source implementation. Change-Id: I17c358288ffe9ef690d702c58723c766d0a0cf21 --- media/libstagefright/NuHTTPDataSource.cpp | 265 ++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 media/libstagefright/NuHTTPDataSource.cpp (limited to 'media/libstagefright/NuHTTPDataSource.cpp') diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp new file mode 100644 index 0000000..8587c1b --- /dev/null +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -0,0 +1,265 @@ +//#define LOG_NDEBUG 0 +#define LOG_TAG "NuHTTPDataSource" +#include + +#include "include/NuHTTPDataSource.h" + +#include +#include + +namespace android { + +static bool ParseSingleUnsignedLong( + const char *from, unsigned long *x) { + char *end; + *x = strtoul(from, &end, 10); + + if (end == from || *end != '\0') { + return false; + } + + return true; +} + +static bool ParseURL( + const char *url, String8 *host, unsigned *port, String8 *path) { + host->setTo(""); + *port = 0; + path->setTo(""); + + if (strncasecmp("http://", url, 7)) { + return false; + } + + const char *slashPos = strchr(&url[7], '/'); + + if (slashPos == NULL) { + host->setTo(&url[7]); + path->setTo("/"); + } else { + host->setTo(&url[7], slashPos - &url[7]); + path->setTo(slashPos); + } + + char *colonPos = strchr(host->string(), ':'); + + if (colonPos != NULL) { + unsigned long x; + if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) { + return false; + } + + *port = x; + + size_t colonOffset = colonPos - host->string(); + String8 tmp(host->string(), colonOffset); + *host = tmp; + } else { + *port = 80; + } + + return true; +} + +NuHTTPDataSource::NuHTTPDataSource() + : mState(DISCONNECTED), + mPort(0), + mOffset(0), + mContentLength(0), + mContentLengthValid(false) { +} + +NuHTTPDataSource::~NuHTTPDataSource() { +} + +status_t NuHTTPDataSource::connect(const char *uri, off_t offset) { + String8 host, path; + unsigned port; + if (!ParseURL(uri, &host, &port, &path)) { + return ERROR_MALFORMED; + } + + return connect(host, port, path, offset); +} + +status_t NuHTTPDataSource::connect( + const char *host, unsigned port, const char *path, off_t offset) { + LOGI("connect to %s:%u%s @%ld", host, port, path, offset); + + bool needsToReconnect = true; + + if (mState == CONNECTED && host == mHost && port == mPort + && offset == mOffset) { + if (mContentLengthValid && mOffset == mContentLength) { + LOGI("Didn't have to reconnect, old one's still good."); + needsToReconnect = false; + } + } + + mHost = host; + mPort = port; + mPath = path; + + status_t err = OK; + + mState = CONNECTING; + + if (needsToReconnect) { + mHTTP.disconnect(); + err = mHTTP.connect(host, port); + } + + if (err != OK) { + mState = DISCONNECTED; + } else if (mState != CONNECTING) { + err = UNKNOWN_ERROR; + } else { + mState = CONNECTED; + + mOffset = offset; + mContentLength = 0; + mContentLengthValid = false; + + String8 request("GET "); + request.append(mPath); + request.append(" HTTP/1.1\r\n"); + request.append("Host: "); + request.append(mHost); + request.append("\r\n"); + + if (offset != 0) { + char rangeHeader[128]; + sprintf(rangeHeader, "Range: bytes=%ld-\r\n", offset); + request.append(rangeHeader); + } + + request.append("\r\n"); + + int httpStatus; + if ((err = mHTTP.send(request.string(), request.size())) != OK + || (err = mHTTP.receive_header(&httpStatus)) != OK) { + mHTTP.disconnect(); + mState = DISCONNECTED; + return err; + } + + if (httpStatus == 302) { + string value; + CHECK(mHTTP.find_header_value("Location", &value)); + + mState = DISCONNECTED; + + mHTTP.disconnect(); + + return connect(value.c_str()); + } + + CHECK(httpStatus >= 200 && httpStatus < 300); + + if (offset == 0) { + string value; + unsigned long x; + if (mHTTP.find_header_value(string("Content-Length"), &value) + && ParseSingleUnsignedLong(value.c_str(), &x)) { + mContentLength = (off_t)x; + mContentLengthValid = true; + } + } else { + string value; + unsigned long x; + if (mHTTP.find_header_value(string("Content-Range"), &value)) { + const char *slashPos = strchr(value.c_str(), '/'); + if (slashPos != NULL + && ParseSingleUnsignedLong(slashPos + 1, &x)) { + mContentLength = x; + mContentLengthValid = true; + } + } + } + } + + return err; +} + +void NuHTTPDataSource::disconnect() { + if (mState == CONNECTING || mState == CONNECTED) { + mHTTP.disconnect(); + } + mState = DISCONNECTED; +} + +status_t NuHTTPDataSource::initCheck() const { + return mState == CONNECTED ? OK : NO_INIT; +} + +ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) { + LOGV("readAt offset %ld, size %d", offset, size); + + Mutex::Autolock autoLock(mLock); + + if (offset != mOffset) { + String8 host = mHost; + String8 path = mPath; + status_t err = connect(host, mPort, path, offset); + + if (err != OK) { + return err; + } + } + + if (mContentLengthValid) { + size_t avail = + (offset >= mContentLength) ? 0 : mContentLength - offset; + + if (size > avail) { + size = avail; + } + } + + size_t numBytesRead = 0; + while (numBytesRead < size) { + ssize_t n = + mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead); + + if (n < 0) { + return n; + } + + numBytesRead += (size_t)n; + + if (n == 0) { + if (mContentLengthValid) { + // We know the content length and made sure not to read beyond + // it and yet the server closed the connection on us. + return ERROR_IO; + } + + break; + } + } + + mOffset += numBytesRead; + + return numBytesRead; +} + +status_t NuHTTPDataSource::getSize(off_t *size) { + *size = 0; + + if (mState != CONNECTED) { + return ERROR_IO; + } + + if (mContentLengthValid) { + *size = mContentLength; + return OK; + } + + return ERROR_UNSUPPORTED; +} + +uint32_t NuHTTPDataSource::flags() { + return kWantsPrefetching; +} + +} // namespace android -- cgit v1.1