summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/httplive/HTTPDownloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/httplive/HTTPDownloader.cpp')
-rw-r--r--media/libstagefright/httplive/HTTPDownloader.cpp273
1 files changed, 273 insertions, 0 deletions
diff --git a/media/libstagefright/httplive/HTTPDownloader.cpp b/media/libstagefright/httplive/HTTPDownloader.cpp
new file mode 100644
index 0000000..3b44bae
--- /dev/null
+++ b/media/libstagefright/httplive/HTTPDownloader.cpp
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2015 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_NDEBUG 0
+#define LOG_TAG "HTTPDownloader"
+#include <utils/Log.h>
+
+#include "HTTPDownloader.h"
+#include "M3UParser.h"
+
+#include <media/IMediaHTTPConnection.h>
+#include <media/IMediaHTTPService.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaHTTP.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/FileSource.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+HTTPDownloader::HTTPDownloader(
+ const sp<IMediaHTTPService> &httpService,
+ const KeyedVector<String8, String8> &headers) :
+ mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
+ mExtraHeaders(headers),
+ mDisconnecting(false) {
+}
+
+void HTTPDownloader::reconnect() {
+ AutoMutex _l(mLock);
+ mDisconnecting = false;
+}
+
+void HTTPDownloader::disconnect() {
+ {
+ AutoMutex _l(mLock);
+ mDisconnecting = true;
+ }
+ mHTTPDataSource->disconnect();
+}
+
+bool HTTPDownloader::isDisconnecting() {
+ AutoMutex _l(mLock);
+ return mDisconnecting;
+}
+
+/*
+ * Illustration of parameters:
+ *
+ * 0 `range_offset`
+ * +------------+-------------------------------------------------------+--+--+
+ * | | | next block to fetch | | |
+ * | | `source` handle => `out` buffer | | | |
+ * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | |
+ * | |<----------- `range_length` / buffer capacity ----------->| |
+ * |<------------------------------ file_size ------------------------------->|
+ *
+ * Special parameter values:
+ * - range_length == -1 means entire file
+ * - block_size == 0 means entire range
+ *
+ */
+ssize_t HTTPDownloader::fetchBlock(
+ const char *url, sp<ABuffer> *out,
+ int64_t range_offset, int64_t range_length,
+ uint32_t block_size, /* download block size */
+ String8 *actualUrl,
+ bool reconnect /* force connect HTTP when resuing source */) {
+ if (isDisconnecting()) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ off64_t size;
+
+ if (reconnect) {
+ if (!strncasecmp(url, "file://", 7)) {
+ mDataSource = new FileSource(url + 7);
+ } else if (strncasecmp(url, "http://", 7)
+ && strncasecmp(url, "https://", 8)) {
+ return ERROR_UNSUPPORTED;
+ } else {
+ KeyedVector<String8, String8> headers = mExtraHeaders;
+ if (range_offset > 0 || range_length >= 0) {
+ headers.add(
+ String8("Range"),
+ String8(
+ AStringPrintf(
+ "bytes=%lld-%s",
+ range_offset,
+ range_length < 0
+ ? "" : AStringPrintf("%lld",
+ range_offset + range_length - 1).c_str()).c_str()));
+ }
+
+ status_t err = mHTTPDataSource->connect(url, &headers);
+
+ if (isDisconnecting()) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ if (err != OK) {
+ return err;
+ }
+
+ mDataSource = mHTTPDataSource;
+ }
+ }
+
+ status_t getSizeErr = mDataSource->getSize(&size);
+
+ if (isDisconnecting()) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ if (getSizeErr != OK) {
+ size = 65536;
+ }
+
+ sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
+ if (*out == NULL) {
+ buffer->setRange(0, 0);
+ }
+
+ ssize_t bytesRead = 0;
+ // adjust range_length if only reading partial block
+ if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
+ range_length = buffer->size() + block_size;
+ }
+ for (;;) {
+ // Only resize when we don't know the size.
+ size_t bufferRemaining = buffer->capacity() - buffer->size();
+ if (bufferRemaining == 0 && getSizeErr != OK) {
+ size_t bufferIncrement = buffer->size() / 2;
+ if (bufferIncrement < 32768) {
+ bufferIncrement = 32768;
+ }
+ bufferRemaining = bufferIncrement;
+
+ ALOGV("increasing download buffer to %zu bytes",
+ buffer->size() + bufferRemaining);
+
+ sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
+ memcpy(copy->data(), buffer->data(), buffer->size());
+ copy->setRange(0, buffer->size());
+
+ buffer = copy;
+ }
+
+ size_t maxBytesToRead = bufferRemaining;
+ if (range_length >= 0) {
+ int64_t bytesLeftInRange = range_length - buffer->size();
+ if (bytesLeftInRange < (int64_t)maxBytesToRead) {
+ maxBytesToRead = bytesLeftInRange;
+
+ if (bytesLeftInRange == 0) {
+ break;
+ }
+ }
+ }
+
+ // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
+ // to help us break out of the loop.
+ ssize_t n = mDataSource->readAt(
+ buffer->size(), buffer->data() + buffer->size(),
+ maxBytesToRead);
+
+ if (isDisconnecting()) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ if (n < 0) {
+ return n;
+ }
+
+ if (n == 0) {
+ break;
+ }
+
+ buffer->setRange(0, buffer->size() + (size_t)n);
+ bytesRead += n;
+ }
+
+ *out = buffer;
+ if (actualUrl != NULL) {
+ *actualUrl = mDataSource->getUri();
+ if (actualUrl->isEmpty()) {
+ *actualUrl = url;
+ }
+ }
+
+ return bytesRead;
+}
+
+ssize_t HTTPDownloader::fetchFile(
+ const char *url, sp<ABuffer> *out, String8 *actualUrl) {
+ ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
+
+ // close off the connection after use
+ mHTTPDataSource->disconnect();
+
+ return err;
+}
+
+sp<M3UParser> HTTPDownloader::fetchPlaylist(
+ const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
+ ALOGV("fetchPlaylist '%s'", url);
+
+ *unchanged = false;
+
+ sp<ABuffer> buffer;
+ String8 actualUrl;
+ ssize_t err = fetchFile(url, &buffer, &actualUrl);
+
+ // close off the connection after use
+ mHTTPDataSource->disconnect();
+
+ if (err <= 0) {
+ return NULL;
+ }
+
+ // MD5 functionality is not available on the simulator, treat all
+ // playlists as changed.
+
+#if defined(HAVE_ANDROID_OS)
+ uint8_t hash[16];
+
+ MD5_CTX m;
+ MD5_Init(&m);
+ MD5_Update(&m, buffer->data(), buffer->size());
+
+ MD5_Final(hash, &m);
+
+ if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
+ // playlist unchanged
+ *unchanged = true;
+
+ return NULL;
+ }
+
+ if (curPlaylistHash != NULL) {
+ memcpy(curPlaylistHash, hash, sizeof(hash));
+ }
+#endif
+
+ sp<M3UParser> playlist =
+ new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
+
+ if (playlist->initCheck() != OK) {
+ ALOGE("failed to parse .m3u8 playlist");
+
+ return NULL;
+ }
+
+ return playlist;
+}
+
+} // namespace android