1/* 2 * Copyright 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17//#define LOG_NDEBUG 0 18#define LOG_TAG "HTTPDownloader" 19#include <utils/Log.h> 20 21#include "HTTPDownloader.h" 22#include "M3UParser.h" 23 24#include <media/IMediaHTTPConnection.h> 25#include <media/IMediaHTTPService.h> 26#include <media/stagefright/foundation/ABuffer.h> 27#include <media/stagefright/foundation/ADebug.h> 28#include <media/stagefright/MediaHTTP.h> 29#include <media/stagefright/DataSource.h> 30#include <media/stagefright/FileSource.h> 31#include <openssl/aes.h> 32#include <openssl/md5.h> 33#include <utils/Mutex.h> 34 35namespace android { 36 37HTTPDownloader::HTTPDownloader( 38 const sp<IMediaHTTPService> &httpService, 39 const KeyedVector<String8, String8> &headers) : 40 mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), 41 mExtraHeaders(headers), 42 mDisconnecting(false) { 43} 44 45void HTTPDownloader::reconnect() { 46 AutoMutex _l(mLock); 47 mDisconnecting = false; 48} 49 50void HTTPDownloader::disconnect() { 51 { 52 AutoMutex _l(mLock); 53 mDisconnecting = true; 54 } 55 mHTTPDataSource->disconnect(); 56} 57 58bool HTTPDownloader::isDisconnecting() { 59 AutoMutex _l(mLock); 60 return mDisconnecting; 61} 62 63/* 64 * Illustration of parameters: 65 * 66 * 0 `range_offset` 67 * +------------+-------------------------------------------------------+--+--+ 68 * | | | next block to fetch | | | 69 * | | `source` handle => `out` buffer | | | | 70 * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | 71 * | |<----------- `range_length` / buffer capacity ----------->| | 72 * |<------------------------------ file_size ------------------------------->| 73 * 74 * Special parameter values: 75 * - range_length == -1 means entire file 76 * - block_size == 0 means entire range 77 * 78 */ 79ssize_t HTTPDownloader::fetchBlock( 80 const char *url, sp<ABuffer> *out, 81 int64_t range_offset, int64_t range_length, 82 uint32_t block_size, /* download block size */ 83 String8 *actualUrl, 84 bool reconnect /* force connect HTTP when resuing source */) { 85 if (isDisconnecting()) { 86 return ERROR_NOT_CONNECTED; 87 } 88 89 off64_t size; 90 91 if (reconnect) { 92 if (!strncasecmp(url, "file://", 7)) { 93 mDataSource = new FileSource(url + 7); 94 } else if (strncasecmp(url, "http://", 7) 95 && strncasecmp(url, "https://", 8)) { 96 return ERROR_UNSUPPORTED; 97 } else { 98 KeyedVector<String8, String8> headers = mExtraHeaders; 99 if (range_offset > 0 || range_length >= 0) { 100 headers.add( 101 String8("Range"), 102 String8( 103 AStringPrintf( 104 "bytes=%lld-%s", 105 range_offset, 106 range_length < 0 107 ? "" : AStringPrintf("%lld", 108 range_offset + range_length - 1).c_str()).c_str())); 109 } 110 111 status_t err = mHTTPDataSource->connect(url, &headers); 112 113 if (isDisconnecting()) { 114 return ERROR_NOT_CONNECTED; 115 } 116 117 if (err != OK) { 118 return err; 119 } 120 121 mDataSource = mHTTPDataSource; 122 } 123 } 124 125 status_t getSizeErr = mDataSource->getSize(&size); 126 127 if (isDisconnecting()) { 128 return ERROR_NOT_CONNECTED; 129 } 130 131 if (getSizeErr != OK) { 132 size = 65536; 133 } 134 135 sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size); 136 if (*out == NULL) { 137 buffer->setRange(0, 0); 138 } 139 140 ssize_t bytesRead = 0; 141 // adjust range_length if only reading partial block 142 if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) { 143 range_length = buffer->size() + block_size; 144 } 145 for (;;) { 146 // Only resize when we don't know the size. 147 size_t bufferRemaining = buffer->capacity() - buffer->size(); 148 if (bufferRemaining == 0 && getSizeErr != OK) { 149 size_t bufferIncrement = buffer->size() / 2; 150 if (bufferIncrement < 32768) { 151 bufferIncrement = 32768; 152 } 153 bufferRemaining = bufferIncrement; 154 155 ALOGV("increasing download buffer to %zu bytes", 156 buffer->size() + bufferRemaining); 157 158 sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining); 159 memcpy(copy->data(), buffer->data(), buffer->size()); 160 copy->setRange(0, buffer->size()); 161 162 buffer = copy; 163 } 164 165 size_t maxBytesToRead = bufferRemaining; 166 if (range_length >= 0) { 167 int64_t bytesLeftInRange = range_length - buffer->size(); 168 if (bytesLeftInRange < (int64_t)maxBytesToRead) { 169 maxBytesToRead = bytesLeftInRange; 170 171 if (bytesLeftInRange == 0) { 172 break; 173 } 174 } 175 } 176 177 // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0) 178 // to help us break out of the loop. 179 ssize_t n = mDataSource->readAt( 180 buffer->size(), buffer->data() + buffer->size(), 181 maxBytesToRead); 182 183 if (isDisconnecting()) { 184 return ERROR_NOT_CONNECTED; 185 } 186 187 if (n < 0) { 188 return n; 189 } 190 191 if (n == 0) { 192 break; 193 } 194 195 buffer->setRange(0, buffer->size() + (size_t)n); 196 bytesRead += n; 197 } 198 199 *out = buffer; 200 if (actualUrl != NULL) { 201 *actualUrl = mDataSource->getUri(); 202 if (actualUrl->isEmpty()) { 203 *actualUrl = url; 204 } 205 } 206 207 return bytesRead; 208} 209 210ssize_t HTTPDownloader::fetchFile( 211 const char *url, sp<ABuffer> *out, String8 *actualUrl) { 212 ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); 213 214 // close off the connection after use 215 mHTTPDataSource->disconnect(); 216 217 return err; 218} 219 220sp<M3UParser> HTTPDownloader::fetchPlaylist( 221 const char *url, uint8_t *curPlaylistHash, bool *unchanged) { 222 ALOGV("fetchPlaylist '%s'", url); 223 224 *unchanged = false; 225 226 sp<ABuffer> buffer; 227 String8 actualUrl; 228 ssize_t err = fetchFile(url, &buffer, &actualUrl); 229 230 // close off the connection after use 231 mHTTPDataSource->disconnect(); 232 233 if (err <= 0) { 234 return NULL; 235 } 236 237 // MD5 functionality is not available on the simulator, treat all 238 // playlists as changed. 239 240#if defined(HAVE_ANDROID_OS) 241 uint8_t hash[16]; 242 243 MD5_CTX m; 244 MD5_Init(&m); 245 MD5_Update(&m, buffer->data(), buffer->size()); 246 247 MD5_Final(hash, &m); 248 249 if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) { 250 // playlist unchanged 251 *unchanged = true; 252 253 return NULL; 254 } 255 256 if (curPlaylistHash != NULL) { 257 memcpy(curPlaylistHash, hash, sizeof(hash)); 258 } 259#endif 260 261 sp<M3UParser> playlist = 262 new M3UParser(actualUrl.string(), buffer->data(), buffer->size()); 263 264 if (playlist->initCheck() != OK) { 265 ALOGE("failed to parse .m3u8 playlist"); 266 267 return NULL; 268 } 269 270 return playlist; 271} 272 273} // namespace android 274