Home | History | Annotate | Download | only in httplive
      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/DataSource.h>
     25 #include <media/MediaHTTPConnection.h>
     26 #include <media/MediaHTTPService.h>
     27 #include <media/stagefright/foundation/ABuffer.h>
     28 #include <media/stagefright/foundation/ADebug.h>
     29 #include <media/stagefright/MediaHTTP.h>
     30 #include <media/stagefright/FileSource.h>
     31 #include <openssl/aes.h>
     32 #include <openssl/md5.h>
     33 #include <utils/Mutex.h>
     34 #include <inttypes.h>
     35 
     36 namespace android {
     37 
     38 HTTPDownloader::HTTPDownloader(
     39         const sp<MediaHTTPService> &httpService,
     40         const KeyedVector<String8, String8> &headers) :
     41     mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
     42     mExtraHeaders(headers),
     43     mDisconnecting(false) {
     44 }
     45 
     46 void HTTPDownloader::reconnect() {
     47     AutoMutex _l(mLock);
     48     mDisconnecting = false;
     49 }
     50 
     51 void HTTPDownloader::disconnect() {
     52     {
     53         AutoMutex _l(mLock);
     54         mDisconnecting = true;
     55     }
     56     mHTTPDataSource->disconnect();
     57 }
     58 
     59 bool HTTPDownloader::isDisconnecting() {
     60     AutoMutex _l(mLock);
     61     return mDisconnecting;
     62 }
     63 
     64 /*
     65  * Illustration of parameters:
     66  *
     67  * 0      `range_offset`
     68  * +------------+-------------------------------------------------------+--+--+
     69  * |            |                                 | next block to fetch |  |  |
     70  * |            | `source` handle => `out` buffer |                     |  |  |
     71  * | `url` file |<--------- buffer size --------->|<--- `block_size` -->|  |  |
     72  * |            |<----------- `range_length` / buffer capacity ----------->|  |
     73  * |<------------------------------ file_size ------------------------------->|
     74  *
     75  * Special parameter values:
     76  * - range_length == -1 means entire file
     77  * - block_size == 0 means entire range
     78  *
     79  */
     80 ssize_t HTTPDownloader::fetchBlock(
     81         const char *url, sp<ABuffer> *out,
     82         int64_t range_offset, int64_t range_length,
     83         uint32_t block_size, /* download block size */
     84         String8 *actualUrl,
     85         bool reconnect /* force connect HTTP when resuing source */) {
     86     if (isDisconnecting()) {
     87         return ERROR_NOT_CONNECTED;
     88     }
     89 
     90     off64_t size;
     91 
     92     if (reconnect) {
     93         if (!strncasecmp(url, "file://", 7)) {
     94             mDataSource = new FileSource(url + 7);
     95         } else if (strncasecmp(url, "http://", 7)
     96                 && strncasecmp(url, "https://", 8)) {
     97             return ERROR_UNSUPPORTED;
     98         } else {
     99             KeyedVector<String8, String8> headers = mExtraHeaders;
    100             if (range_offset > 0 || range_length >= 0) {
    101                 headers.add(
    102                         String8("Range"),
    103                         String8(
    104                             AStringPrintf(
    105                                 "bytes=%lld-%s",
    106                                 range_offset,
    107                                 range_length < 0
    108                                     ? "" : AStringPrintf("%lld",
    109                                             range_offset + range_length - 1).c_str()).c_str()));
    110             }
    111 
    112             status_t err = mHTTPDataSource->connect(url, &headers);
    113 
    114             if (isDisconnecting()) {
    115                 return ERROR_NOT_CONNECTED;
    116             }
    117 
    118             if (err != OK) {
    119                 return err;
    120             }
    121 
    122             mDataSource = mHTTPDataSource;
    123         }
    124     }
    125 
    126     status_t getSizeErr = mDataSource->getSize(&size);
    127 
    128     if (isDisconnecting()) {
    129         return ERROR_NOT_CONNECTED;
    130     }
    131 
    132     if (getSizeErr != OK) {
    133         size = 65536;
    134     }
    135 
    136     sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
    137     if (*out == NULL) {
    138         buffer->setRange(0, 0);
    139     }
    140 
    141     ssize_t bytesRead = 0;
    142     // adjust range_length if only reading partial block
    143     if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
    144         range_length = buffer->size() + block_size;
    145     }
    146     for (;;) {
    147         // Only resize when we don't know the size.
    148         size_t bufferRemaining = buffer->capacity() - buffer->size();
    149         if (bufferRemaining == 0 && getSizeErr != OK) {
    150             size_t bufferIncrement = buffer->size() / 2;
    151             if (bufferIncrement < 32768) {
    152                 bufferIncrement = 32768;
    153             }
    154             bufferRemaining = bufferIncrement;
    155 
    156             ALOGV("increasing download buffer to %zu bytes",
    157                  buffer->size() + bufferRemaining);
    158 
    159             sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
    160             memcpy(copy->data(), buffer->data(), buffer->size());
    161             copy->setRange(0, buffer->size());
    162 
    163             buffer = copy;
    164         }
    165 
    166         size_t maxBytesToRead = bufferRemaining;
    167         if (range_length >= 0) {
    168             int64_t bytesLeftInRange = range_length - buffer->size();
    169             if (bytesLeftInRange < 0) {
    170                 ALOGE("range_length %" PRId64 " wrapped around", range_length);
    171                 return ERROR_OUT_OF_RANGE;
    172             } else if (bytesLeftInRange < (int64_t)maxBytesToRead) {
    173                 maxBytesToRead = bytesLeftInRange;
    174 
    175                 if (bytesLeftInRange == 0) {
    176                     break;
    177                 }
    178             }
    179         }
    180 
    181         // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
    182         // to help us break out of the loop.
    183         ssize_t n = mDataSource->readAt(
    184                 buffer->size(), buffer->data() + buffer->size(),
    185                 maxBytesToRead);
    186 
    187         if (isDisconnecting()) {
    188             return ERROR_NOT_CONNECTED;
    189         }
    190 
    191         if (n < 0) {
    192             return n;
    193         }
    194 
    195         if (n == 0) {
    196             break;
    197         }
    198 
    199         buffer->setRange(0, buffer->size() + (size_t)n);
    200         bytesRead += n;
    201     }
    202 
    203     *out = buffer;
    204     if (actualUrl != NULL) {
    205         *actualUrl = mDataSource->getUri();
    206         if (actualUrl->isEmpty()) {
    207             *actualUrl = url;
    208         }
    209     }
    210 
    211     return bytesRead;
    212 }
    213 
    214 ssize_t HTTPDownloader::fetchFile(
    215         const char *url, sp<ABuffer> *out, String8 *actualUrl) {
    216     ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
    217 
    218     // close off the connection after use
    219     mHTTPDataSource->disconnect();
    220 
    221     return err;
    222 }
    223 
    224 sp<M3UParser> HTTPDownloader::fetchPlaylist(
    225         const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
    226     ALOGV("fetchPlaylist '%s'", url);
    227 
    228     *unchanged = false;
    229 
    230     sp<ABuffer> buffer;
    231     String8 actualUrl;
    232     ssize_t err = fetchFile(url, &buffer, &actualUrl);
    233 
    234     // close off the connection after use
    235     mHTTPDataSource->disconnect();
    236 
    237     if (err <= 0) {
    238         return NULL;
    239     }
    240 
    241     // MD5 functionality is not available on the simulator, treat all
    242     // playlists as changed.
    243 
    244 #if defined(__ANDROID__)
    245     uint8_t hash[16];
    246 
    247     MD5_CTX m;
    248     MD5_Init(&m);
    249     MD5_Update(&m, buffer->data(), buffer->size());
    250 
    251     MD5_Final(hash, &m);
    252 
    253     if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
    254         // playlist unchanged
    255         *unchanged = true;
    256 
    257         return NULL;
    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 #if defined(__ANDROID__)
    271     if (curPlaylistHash != NULL) {
    272 
    273         memcpy(curPlaylistHash, hash, sizeof(hash));
    274     }
    275 #endif
    276 
    277     return playlist;
    278 }
    279 
    280 }  // namespace android
    281