Android HLS协议相关记录及部分解析

github:AndroidVideoServer(参考库)

Android 底层实现HLS协议的部分解析

由于目前网络不好,暂时先记录想到的,因为HLS是最近开始学习研究的,害怕最近项目忙忘记,所以先记录下(以下位于LibStageFright):

  • Android.mk
  • HTTPDownloader.cpp
  • HTTPDownloader.h
  • LiveDataSource.cpp
  • LiveDataSource.h
  • LiveSession.cpp
  • LiveSession.h
  • M3UParser.cpp
  • M3UParser.h
  • MODULE_LICENSE_APACHE2
  • PlaylistFetcher.cpp
  • PlaylistFetcher.h

上面三个算是底层主要是的核心东西.我先大致讲一下Android手机播放HLS的流程.

  • 首先Android给VideoView设置Url.
  • 接下来设置Url后,就会进入JNI创建MediaPlayerService,然后与底层进行Binder通信.
  • 然后会在底层创建一个MediaPlayerService,在MediaPlayerServices中的Create方法中会判断类型.
  • 根据Url地址进行判断,如果是Http或者Https开头的,然后是否包含了M3u8的就创建一个NuPlayerDriver.
  • 其实底层核心的是StrageFright框架.创建了具体的播放对象后,会判断是否采用硬件解码,并进行解码播放工作.

MediaPlayerService(底层)

连接:https://android.googlesource.com/platform/frameworks/av/+/jb-release/media/libmediaplayerservice/MediaPlayerService.cpp

主要是根据url地址创建不同的播放器对象,具体代码如下:

    static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
            notify_callback_f notifyFunc)
    {
        sp<MediaPlayerBase> p;
        switch (playerType) {     // 根据类型创建不同的播放器对象.
            case SONIVOX_PLAYER:
                ALOGV(" create MidiFile");
                p = new MidiFile();
                break;
            case STAGEFRIGHT_PLAYER:
                ALOGV(" create StagefrightPlayer");
                p = new StagefrightPlayer;
                break;
            case NU_PLAYER:
                ALOGV(" create NuPlayer");
                p = new NuPlayerDriver; //专门用来播放HLS协议的播放器对象
                break;
            case TEST_PLAYER:
                ALOGV("Create Test Player stub");
                p = new TestPlayerStub();
                break;
            case AAH_RX_PLAYER:
                ALOGV(" create A@H RX Player");
                p = createAAH_RXPlayer();
                break;
            case AAH_TX_PLAYER:
                ALOGV(" create A@H TX Player");
                p = createAAH_TXPlayer();
                break;
            default:
                ALOGE("Unknown player type: %d", playerType);
                return NULL;
        }
        if (p != NULL) {
            if (p->initCheck() == NO_ERROR) {
                p->setNotifyCallback(cookie, notifyFunc);
            } else {
                p.clear();
            }
        }
        if (p == NULL) {
            ALOGE("Failed to create player object");
        }
        return p;
    }
那么我们去看看PlayerType在哪儿获得的,怎么获得的:
    player_type getPlayerType(const char* url)
    {
        if (TestPlayerStub::canBeUsed(url)) {
            return TEST_PLAYER;
        }
        if (!strncasecmp("http://", url, 7)
                || !strncasecmp("https://", url, 8)) {  //判断是否为https,或者http的
            size_t len = strlen(url);
            if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) { // 并且是有一个有效的m3u8结尾的url
                return NU_PLAYER;
            }
            if (strstr(url,"m3u8")) {
                return NU_PLAYER;
            }
        }
        if (!strncasecmp("rtsp://", url, 7)) {
            return NU_PLAYER;
        }
        if (!strncasecmp("aahRX://", url, 8)) {
            return AAH_RX_PLAYER;
        }
        // use MidiFile for MIDI extensions
        int lenURL = strlen(url);
        for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
            int len = strlen(FILE_EXTS[i].extension);
            int start = lenURL - len;
            if (start > 0) {
                if (!strncasecmp(url + start, FILE_EXTS[i].extension, len)) {
                    return FILE_EXTS[i].playertype;
                }
            }
        }
        return getDefaultPlayerType(); // 如果之前都没有,那么返回默认的.
    }

其中遇到的问题有:

  • 如何实现边看边缓存ts — 已经解决(方法不是很好)
  • 拖拽重复请求 —- 看源码正在寻求解决方案
  • Android底层是如何缓存ts或者轮训播放的 —- 自己猜想过,目前看到了一部分源码可能是,但是还在着手看.
  • Android 3.0以下如何支持HLS — 看VCL源码中…
HttpLiveSession去获取Ts
    for (size_t i = 0; i < kNumSources; ++i) { // 创建了2个获取器,去获取TS资源.
            mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
            mPacketSources2.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
        }
将数据交给NuPlayerDriver
    sp<AnotherPacketSource> LiveSession::getMetadataSource(
            sp<AnotherPacketSource> sources[kNumSources], uint32_t streamMask, bool newUri) {
        // todo: One case where the following strategy can fail is when audio and video
        // are in separate playlists, both are transport streams, and the metadata
        // is actually contained in the audio stream.
        ALOGV("[timed_id3] getMetadataSourceForUri streamMask %x newUri %s",
                streamMask, newUri ? "true" : "false");
        if ((sources[kVideoIndex] != NULL) // video fetcher; or ...
                || (!(streamMask & STREAMTYPE_VIDEO) && sources[kAudioIndex] != NULL)) {
                // ... audio fetcher for audio only variant
            return getPacketSourceForStreamIndex(kMetaDataIndex, newUri);
        }
        return NULL;
    }
PlaylistFetcher

看名字就知道是来获取PlayList索引文件里面的数据,比如ts解析每个字段什么的.
我们看看它具体做了些什么:

    PlaylistFetcher::PlaylistFetcher( // 很多参数
            const sp<AMessage> &notify,
            const sp<LiveSession> &session,
            const char *uri,
            int32_t id,
            int32_t subtitleGeneration)
        : mNotify(notify),
          mSession(session),
          mURI(uri),
          mFetcherID(id),
          mStreamTypeMask(0),
          mStartTimeUs(-1ll),
          mSegmentStartTimeUs(-1ll),
          mDiscontinuitySeq(-1ll),
          mStartTimeUsRelative(false),
          mLastPlaylistFetchTimeUs(-1ll),
          mPlaylistTimeUs(-1ll),
          mSeqNumber(-1),
          mNumRetries(0),
          mStartup(true),
          mIDRFound(false),
          mSeekMode(LiveSession::kSeekModeExactPosition),
          mTimeChangeSignaled(false),
          mNextPTSTimeUs(-1ll),
          mMonitorQueueGeneration(0),
          mSubtitleGeneration(subtitleGeneration),
          mLastDiscontinuitySeq(-1ll),
          mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY),
          mFirstPTSValid(false),
          mFirstTimeUs(-1ll),
          mVideoBuffer(new AnotherPacketSource(NULL)),
          mThresholdRatio(-1.0f),
          mDownloadState(new DownloadState()),
          mHasMetadata(false) {
        memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
        mHTTPDownloader = mSession->getHTTPDownloader(); // HttpLiveSession对象去获取DownLoader.
    }


    // 看到LiveSession中去直接创建了一个HTTPDownloader对象.
    sp<HTTPDownloader> LiveSession::getHTTPDownloader() {
        return new HTTPDownloader(mHTTPService, mExtraHeaders);
    }

    /*我们看看HTTPDownloader,这个对象主要有三个方法:fetchBlock(),fetchFile(),fetchPlaylist()*/
    HTTPDownloader::HTTPDownloader( //构造函数
            const sp<IMediaHTTPService> &httpService,
            const KeyedVector<String8, String8> &headers) :
        mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), // MediaHttp的连接对象.
        mExtraHeaders(headers),
        mDisconnecting(false) {
    }

    //fetchFile方法主要是获取Ts文件
    ssize_t HTTPDownloader::fetchFile(
        const char *url, sp<ABuffer> *out, String8 *actualUrl) {
        ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); // 可以看到主要是调用了fetchBlock方法.
        // close off the connection after use
        mHTTPDataSource->disconnect();
        return err;
    }

    //那我们接下来去看看fetchBlock,这个方法有点复杂,大致就是获取流媒体内存区域的块
    /*
     * 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;
    }


    //接下来就是:fetchPlaylist方法.
    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(__ANDROID__)
        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());//  可以看到去解析了M3u8文件.
        if (playlist->initCheck() != OK) { // 检查是否解析成功的.
            ALOGE("failed to parse .m3u8 playlist");
            return NULL;
        }
        return playlist; // 得到一个M3UParser对象.
    }


    //接下来看看M3UParser吧,这个一看名字就知道是在解析M3u8文件,然后解析m3u8根据Hls的版本解析.
    status_t M3UParser::parse(const void *_data, size_t size) {
        int32_t lineNo = 0;
        sp<AMessage> itemMeta;
        const char *data = (const char *)_data;
        size_t offset = 0;
        uint64_t segmentRangeOffset = 0;
        while (offset < size) {
            size_t offsetLF = offset;
            while (offsetLF < size && data[offsetLF] != '\n') {
                ++offsetLF;
            }
            AString line;
            if (offsetLF > offset && data[offsetLF - 1] == '\r') {
                line.setTo(&data[offset], offsetLF - offset - 1);
            } else {
                line.setTo(&data[offset], offsetLF - offset);
            }
            // ALOGI("#%s#", line.c_str());
            if (line.empty()) {
                offset = offsetLF + 1;
                continue;
            }
            if (lineNo == 0 && line == "#EXTM3U") { //是否为M3u8协议头
                mIsExtM3U = true;
            }
            if (mIsExtM3U) {
                status_t err = OK;
                if (line.startsWith("#EXT-X-TARGETDURATION")) {
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    err = parseMetaData(line, &mMeta, "target-duration");
                } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { //协议序列
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    err = parseMetaData(line, &mMeta, "media-sequence");
                } else if (line.startsWith("#EXT-X-KEY")) { // url地址key
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    err = parseCipherInfo(line, &itemMeta, mBaseURI);
                } else if (line.startsWith("#EXT-X-ENDLIST")) { // 结束tag
                    mIsComplete = true;
                } else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) {
                    mIsEvent = true;
                } else if (line.startsWith("#EXTINF")) { // 每一个item的开头
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    err = parseMetaDataDuration(line, &itemMeta, "durationUs");
                } else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    if (itemMeta == NULL) {
                        itemMeta = new AMessage;
                    }
                    itemMeta->setInt32("discontinuity", true);
                    ++mDiscontinuityCount;
                } else if (line.startsWith("#EXT-X-STREAM-INF")) {
                    if (mMeta != NULL) {
                        return ERROR_MALFORMED;
                    }
                    mIsVariantPlaylist = true;
                    err = parseStreamInf(line, &itemMeta);
                } else if (line.startsWith("#EXT-X-BYTERANGE")) {
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    uint64_t length, offset;
                    err = parseByteRange(line, segmentRangeOffset, &length, &offset);
                    if (err == OK) {
                        if (itemMeta == NULL) {
                            itemMeta = new AMessage;
                        }
                        itemMeta->setInt64("range-offset", offset);
                        itemMeta->setInt64("range-length", length);
                        segmentRangeOffset = offset + length;
                    }
                } else if (line.startsWith("#EXT-X-MEDIA")) {
                    err = parseMedia(line);
                } else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) {
                    if (mIsVariantPlaylist) {
                        return ERROR_MALFORMED;
                    }
                    size_t seq;
                    err = parseDiscontinuitySequence(line, &seq);
                    if (err == OK) {
                        mDiscontinuitySeq = seq;
                    }
                }
                if (err != OK) {
                    return err;
                }
            }
            if (!line.startsWith("#")) {
                if (!mIsVariantPlaylist) {
                    int64_t durationUs;
                    if (itemMeta == NULL
                            || !itemMeta->findInt64("durationUs", &durationUs)) {
                        return ERROR_MALFORMED;
                    }
                    itemMeta->setInt32("discontinuity-sequence",
                            mDiscontinuitySeq + mDiscontinuityCount);
                }
                mItems.push();
                Item *item = &mItems.editItemAt(mItems.size() - 1);
                CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI));
                item->mMeta = itemMeta;
                itemMeta.clear();
            }
            offset = offsetLF + 1;
            ++lineNo;
        }
        // error checking of all fields that's required to appear once
        // (currently only checking "target-duration"), and
        // initialization of playlist properties (eg. mTargetDurationUs)
        if (!mIsVariantPlaylist) {
            int32_t targetDurationSecs;
            if (mMeta == NULL || !mMeta->findInt32(
                    "target-duration", &targetDurationSecs)) {
                ALOGE("Media playlist missing #EXT-X-TARGETDURATION");
                return ERROR_MALFORMED;
            }
            mTargetDurationUs = targetDurationSecs * 1000000ll;
            mFirstSeqNumber = 0;
            if (mMeta != NULL) {
                mMeta->findInt32("media-sequence", &mFirstSeqNumber);
            }
            mLastSeqNumber = mFirstSeqNumber + mItems.size() - 1;
        }
        return OK;
    }


   /*好了以上大致流程看完了,留下一个问题,我们在播放ts的时候什么时候去下载下一个ts呢.?*/
    void PlaylistFetcher::DownloadState::saveState( // 存储一些状态
            AString &uri,
            sp<AMessage> &itemMeta,
            sp<ABuffer> &buffer,
            sp<ABuffer> &tsBuffer,
            int32_t &firstSeqNumberInPlaylist,
            int32_t &lastSeqNumberInPlaylist) {
        mHasSavedState = true;
        mUri = uri; // 当前URi地址
        mItemMeta = itemMeta; // 当前播放的数据
        mBuffer = buffer; // 当前的Buffer
        mTsBuffer = tsBuffer; // 当前ts的Buffer
        mFirstSeqNumberInPlaylist = firstSeqNumberInPlaylist; // 主要是看到有2个序列
        mLastSeqNumberInPlaylist = lastSeqNumberInPlaylist;
    }
上面看到了留着一个问题,就是什么时候去下载下一个ts呢.我们要去看看HttpDownLoader?之前也说就只有几个方法,所以肯定不在这儿.真正实现其实是在PlayListFetcher
    void PlaylistFetcher::onMonitorQueue() {
        // in the middle of an unfinished download, delay
        // playlist refresh as it'll change seq numbers
        if (!mDownloadState->hasSavedState()) {
            refreshPlaylist();
        }
        int64_t targetDurationUs = kMinBufferedDurationUs;
        if (mPlaylist != NULL) {
            targetDurationUs = mPlaylist->getTargetDuration();
        }
        int64_t bufferedDurationUs = 0ll;
        status_t finalResult = OK;
        if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
            sp<AnotherPacketSource> packetSource =
                mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
            bufferedDurationUs =
                    packetSource->getBufferedDurationUs(&finalResult);
        } else {
            // Use min stream duration, but ignore streams that never have any packet
            // enqueued to prevent us from waiting on a non-existent stream;
            // when we cannot make out from the manifest what streams are included in
            // a playlist we might assume extra streams.
            bufferedDurationUs = -1ll;
            for (size_t i = 0; i < mPacketSources.size(); ++i) {
                if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0
                        || mPacketSources[i]->getLatestEnqueuedMeta() == NULL) {
                    continue;
                }
                int64_t bufferedStreamDurationUs =
                    mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
                FSLOGV(mPacketSources.keyAt(i), "buffered %lld", (long long)bufferedStreamDurationUs);
                if (bufferedDurationUs == -1ll
                     || bufferedStreamDurationUs < bufferedDurationUs) {
                    bufferedDurationUs = bufferedStreamDurationUs;
                }
            }
            if (bufferedDurationUs == -1ll) {
                bufferedDurationUs = 0ll;
            }
        }
        if (finalResult == OK && bufferedDurationUs < kMinBufferedDurationUs) {
            FLOGV("monitoring, buffered=%lld < %lld",
                    (long long)bufferedDurationUs, (long long)kMinBufferedDurationUs);
            // delay the next download slightly; hopefully this gives other concurrent fetchers
            // a better chance to run.
            // onDownloadNext(); //这儿文档也说的很明白了.其实就是handler机制,进行通信,所以主要what就是kWhatDownloadNext,我们去搜一下kWhatDownloadNext吧.
            sp<AMessage> msg = new AMessage(kWhatDownloadNext, this);
            msg->setInt32("generation", mMonitorQueueGeneration);
            msg->post(1000l);
        } else {
            // We'd like to maintain buffering above durationToBufferUs, so try
            // again when buffer just about to go below durationToBufferUs
            // (or after targetDurationUs / 2, whichever is smaller).
            int64_t delayUs = bufferedDurationUs - kMinBufferedDurationUs + 1000000ll;
            if (delayUs > targetDurationUs / 2) {
                delayUs = targetDurationUs / 2;
            }
            FLOGV("pausing for %lld, buffered=%lld > %lld",
                    (long long)delayUs,
                    (long long)bufferedDurationUs,
                    (long long)kMinBufferedDurationUs);
            postMonitorQueue(delayUs);
        }
    }


    //搜到了.kWhatDownloadNext
    void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
        switch (msg->what()) {
            case kWhatStart:
            {
                status_t err = onStart(msg);
                sp<AMessage> notify = mNotify->dup();
                notify->setInt32("what", kWhatStarted);
                notify->setInt32("err", err);
                notify->post();
                break;
            }
            case kWhatPause:
            {
                onPause();
                sp<AMessage> notify = mNotify->dup();
                notify->setInt32("what", kWhatPaused);
                notify->setInt32("seekMode",
                        mDownloadState->hasSavedState()
                        ? LiveSession::kSeekModeNextSample
                        : LiveSession::kSeekModeNextSegment);
                notify->post();
                break;
            }
            case kWhatStop:
            {
                onStop(msg);
                sp<AMessage> notify = mNotify->dup();
                notify->setInt32("what", kWhatStopped);
                notify->post();
                break;
            }
            case kWhatFetchPlaylist:
            {
                bool unchanged;
                sp<M3UParser> playlist = mHTTPDownloader->fetchPlaylist(
                        mURI.c_str(), NULL /* curPlaylistHash */, &unchanged);
                sp<AMessage> notify = mNotify->dup();
                notify->setInt32("what", kWhatPlaylistFetched);
                notify->setObject("playlist", playlist);
                notify->post();
                break;
            }
            case kWhatMonitorQueue:
            case kWhatDownloadNext: // 看到了吧,下载下一个,其中有一个错误的延时1s重新下载的机制.,最有才会放弃
            {
                int32_t generation;
                CHECK(msg->findInt32("generation", &generation));
                if (generation != mMonitorQueueGeneration) {
                    // Stale event
                    break;
                }
                if (msg->what() == kWhatMonitorQueue) { // retry机制
                    onMonitorQueue();
                } else { //  否则下载下一个.
                    onDownloadNext();
                }
                break;
            }
            case kWhatResumeUntil:
            {
                onResumeUntil(msg);
                break;
            }
            default:
                TRESPASS();
        }
    }

以上都是纯手打记录下,等空闲了,把代码和UML图都贴出来更加仔细的分析.因为最近项目太忙了,担心忘记了.还有一些底层一些工作流程也会陆续记录下.坚持.!!!

随后会把边看边缓存ts这个实现方案发出来,目前都没有找到好的解决方案.求大神指导.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值