/*
 * Copyright (C) 2010 The Android Open Source Project
 * Copyright (C) 2010 Sony Ericsson Mobile Communications AB.
 *
 * 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.
 *
 * NOTE: This file has been modified by Sony Ericsson Mobile Communications AB.
 * Modifications are licensed under the License.
 */

#ifndef MY_HANDLER_H_

#define MY_HANDLER_H_

//#define LOG_NDEBUG 0
#define LOG_TAG "MyHandler"
#include <utils/Log.h>

#include "APacketSource.h"
#include "ARTPConnection.h"
#include "ARTSPConnection.h"
#include "ASessionDescription.h"

#include <ctype.h>
#include <cutils/properties.h>

#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/MediaErrors.h>

#include <arpa/inet.h>
#include <sys/socket.h>

// If no access units are received within 30 secs, assume that the rtp
// stream has ended and signal end of stream.
static int64_t kAccessUnitTimeoutUs = 30000000ll;

// If no access units arrive for the first 10 secs after starting the
// stream, assume none ever will and signal EOS or switch transports.
static int64_t kStartupTimeoutUs = 10000000ll;

//Wait 500ms before the seek command is executed
//in order to receive new seek positions
static int64_t kDelaySeek = 500000ll;

//Default is the OPTIONS message sent every 60 secs if not a timeout
//is specified in the SETUP reply.
static int64_t kOptionsTimeoutUs = 60000000ll;

namespace android {

static void MakeUserAgentString(AString *s) {
    s->setTo("stagefright/1.1 (Linux;Android ");

#if (PROPERTY_VALUE_MAX < 8)
#error "PROPERTY_VALUE_MAX must be at least 8"
#endif

    char value[PROPERTY_VALUE_MAX];
    property_get("ro.build.version.release", value, "Unknown");
    s->append(value);
    s->append(")");
}

static bool GetAttribute(const char *s, const char *key, AString *value) {
    value->clear();

    size_t keyLen = strlen(key);

    for (;;) {
        while (isspace(*s)) {
            ++s;
        }

        const char *colonPos = strchr(s, ';');

        size_t len =
            (colonPos == NULL) ? strlen(s) : colonPos - s;

        if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) {
            value->setTo(&s[keyLen + 1], len - keyLen - 1);
            return true;
        }

        if (colonPos == NULL) {
            return false;
        }

        s = colonPos + 1;
    }
}

struct MyHandler : public AHandler {
    MyHandler(const char *url, const sp<ALooper> &looper)
        : mLooper(looper),
          mNetLooper(new ALooper),
          mConn(new ARTSPConnection),
          mRTPConn(new ARTPConnection),
          mOriginalSessionURL(url),
          mSessionURL(url),
          mSetupTracksSuccessful(false),
          mSeekPending(false),
          mFirstAccessUnit(true),
          mFirstAccessUnitNTP(0),
          mNumAccessUnitsReceived(0),
          mCheckPending(false),
          mCheckGeneration(0),
          mTimeoutValue(0),
          mTryTCPInterleaving(false),
          mTryFakeRTCP(false),
          mReceivedFirstRTCPPacket(false),
          mReceivedFirstRTPPacket(false),
          mSeekable(false),
          mPausing(false),
          mOptionsSent(false),
          mIsLiveStream(false),
          mStartTimeUs(0) {
        mNetLooper->setName("rtsp net");
        mNetLooper->start(false /* runOnCallingThread */,
                          false /* canCallJava */,
                          PRIORITY_HIGHEST);

        // Strip any authentication info from the session url, we don't
        // want to transmit user/pass in cleartext.
        AString host, path, user, pass;
        unsigned port;
        if (ARTSPConnection::ParseURL(
                    mSessionURL.c_str(), &host, &port, &path, &user, &pass)
                && user.size() > 0) {
            mSessionURL.clear();
            mSessionURL.append("rtsp://");
            mSessionURL.append(host);
            mSessionURL.append(":");
            mSessionURL.append(StringPrintf("%u", port));
            mSessionURL.append(path);

            LOGI("rewritten session url: '%s'", mSessionURL.c_str());
        }
    }

    void connect(const sp<AMessage> &doneMsg, int64_t startTimeUs) {
        mConnectDoneMsg = doneMsg;
        mStartTimeUs = startTimeUs;

        mLooper->registerHandler(this);
        mLooper->registerHandler(mConn);
        (1 ? mNetLooper : mLooper)->registerHandler(mRTPConn);

        sp<AMessage> notify = new AMessage('biny', id());
        mConn->observeBinaryData(notify);

        sp<AMessage> reply = new AMessage('conn', id());
        mConn->connect(mOriginalSessionURL.c_str(), reply);
    }

    void disconnect(const sp<AMessage> &doneMsg) {
        mDisconnectDoneMsg = doneMsg;

        (new AMessage('abor', id()))->post();
    }

    void seek(int64_t timeUs, const sp<AMessage> &doneMsg) {
        sp<AMessage> msg = new AMessage('seek', id());
        msg->setInt64("time", timeUs);
        msg->setMessage("doneMsg", doneMsg);
        msg->post();
    }

    void pause() {
        sp<AMessage> msg = new AMessage('paus', id());
        msg->post();
    }

    void play() {
        sp<AMessage> msg = new AMessage('resu', id());
        msg->post();
    }

    int64_t getNormalPlayTimeUs() {
        int64_t maxTimeUs = 0;
        for (size_t i = 0; i < mTracks.size(); ++i) {
            int64_t timeUs = mTracks.editItemAt(i).mPacketSource
                ->getNormalPlayTimeUs();

            if (i == 0 || timeUs > maxTimeUs) {
                maxTimeUs = timeUs;
            }
        }

        return maxTimeUs;
    }

    static void addRR(const sp<ABuffer> &buf) {
        uint8_t *ptr = buf->data() + buf->size();
        ptr[0] = 0x80 | 0;
        ptr[1] = 201;  // RR
        ptr[2] = 0;
        ptr[3] = 1;
        ptr[4] = 0xde;  // SSRC
        ptr[5] = 0xad;
        ptr[6] = 0xbe;
        ptr[7] = 0xef;

        buf->setRange(0, buf->size() + 8);
    }

    static void addSDES(int s, const sp<ABuffer> &buffer) {
        struct sockaddr_in addr;
        socklen_t addrSize = sizeof(addr);
        CHECK_EQ(0, getsockname(s, (sockaddr *)&addr, &addrSize));

        uint8_t *data = buffer->data() + buffer->size();
        data[0] = 0x80 | 1;
        data[1] = 202;  // SDES
        data[4] = 0xde;  // SSRC
        data[5] = 0xad;
        data[6] = 0xbe;
        data[7] = 0xef;

        size_t offset = 8;

        data[offset++] = 1;  // CNAME

        AString cname = "stagefright@";
        cname.append(inet_ntoa(addr.sin_addr));
        data[offset++] = cname.size();

        memcpy(&data[offset], cname.c_str(), cname.size());
        offset += cname.size();

        data[offset++] = 6;  // TOOL

        AString tool;
        MakeUserAgentString(&tool);

        data[offset++] = tool.size();

        memcpy(&data[offset], tool.c_str(), tool.size());
        offset += tool.size();

        data[offset++] = 0;

        if ((offset % 4) > 0) {
            size_t count = 4 - (offset % 4);
            switch (count) {
                case 3:
                    data[offset++] = 0;
                case 2:
                    data[offset++] = 0;
                case 1:
                    data[offset++] = 0;
            }
        }

        size_t numWords = (offset / 4) - 1;
        data[2] = numWords >> 8;
        data[3] = numWords & 0xff;

        buffer->setRange(buffer->offset(), buffer->size() + offset);
    }

    // In case we're behind NAT, fire off two UDP packets to the remote
    // rtp/rtcp ports to poke a hole into the firewall for future incoming
    // packets. We're going to send an RR/SDES RTCP packet to both of them.
    void pokeAHole(int rtpSocket, int rtcpSocket, const AString &transport) {
        AString source;
        AString server_port;

        if (!GetAttribute(transport.c_str(), "source", &source)) {
            LOGV("No source specified in transport");
            AString globalCInfo;
            if (mSessionDesc->findAttribute(0, "c=", &globalCInfo)) {
                const char* cInfo = globalCInfo.c_str();
                const char* spacePos = strchr(cInfo, ' ');
                if (spacePos == NULL) {
                    return;
                }

                spacePos = strchr(spacePos +1, ' ');
                if (spacePos == NULL) {
                    return;
                }

                spacePos++;

                const char* slashPos = strchr(spacePos, '/');
                if (slashPos == NULL) {
                    // c=IN IP4 224.2.1.1
                    source = AString(spacePos);
                } else {
                    // c=IN IP4 224.2.1.1/127
                    // c=IN IP4 224.2.1.1/127/3
                    source.append(spacePos, slashPos-spacePos);
                }

            } else {
                LOGV("No global c= available");
                return;
            }
        }

        if (!GetAttribute(transport.c_str(), "server_port", &server_port)) {
            LOGV("No server_port specified");
            return;
        }

        int rtpPort, rtcpPort;
        if (sscanf(server_port.c_str(), "%d-%d", &rtpPort, &rtcpPort) != 2
                || rtpPort <= 0 || rtpPort > 65535
                || rtcpPort <=0 || rtcpPort > 65535
                || rtcpPort != rtpPort + 1
                || (rtpPort & 1) != 0) {
            return;
        }

        struct sockaddr_in addr;
        memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(source.c_str());

        if (addr.sin_addr.s_addr == INADDR_NONE) {
            return;
        }

        // Make up an RR/SDES RTCP packet.
        sp<ABuffer> buf = new ABuffer(65536);
        buf->setRange(0, 0);
        addRR(buf);
        addSDES(rtpSocket, buf);

        addr.sin_port = htons(rtpPort);

        ssize_t n = sendto(
                rtpSocket, buf->data(), buf->size(), 0,
                (const sockaddr *)&addr, sizeof(addr));
        CHECK_EQ(n, (ssize_t)buf->size());

        addr.sin_port = htons(rtcpPort);

        n = sendto(
                rtcpSocket, buf->data(), buf->size(), 0,
                (const sockaddr *)&addr, sizeof(addr));
        CHECK_EQ(n, (ssize_t)buf->size());

        LOGV("successfully poked holes at %s ports %d and %d", source.c_str(), rtpPort, rtcpPort);
    }

    virtual void onMessageReceived(const sp<AMessage> &msg) {
        switch (msg->what()) {
            case 'conn':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("connection request completed with result %d (%s)",
                     result, strerror(-result));

                if (result == OK) {
                    AString request;
                    request = "DESCRIBE ";
                    request.append(mSessionURL);
                    request.append(" RTSP/1.0\r\n");
                    request.append("Accept: application/sdp\r\n");
                    request.append("\r\n");

                    sp<AMessage> reply = new AMessage('desc', id());
                    mConn->sendRequest(request.c_str(), reply);
                } else {
                    (new AMessage('disc', id()))->post();
                }
                break;
            }

            case 'disc':
            {
                int32_t reconnect;
                if (msg->findInt32("reconnect", &reconnect) && reconnect) {
                    sp<AMessage> reply = new AMessage('conn', id());
                    mConn->connect(mOriginalSessionURL.c_str(), reply);
                } else {
                    (new AMessage('quit', id()))->post();
                }
                break;
            }

            case 'desc':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("DESCRIBE completed with result %d (%s)",
                     result, strerror(-result));

                if (result == OK) {
                    sp<RefBase> obj;
                    CHECK(msg->findObject("response", &obj));
                    sp<ARTSPResponse> response =
                        static_cast<ARTSPResponse *>(obj.get());

                    if (response->mStatusCode == 302) {
                        ssize_t i = response->mHeaders.indexOfKey("location");
                        CHECK_GE(i, 0);

                        mSessionURL = response->mHeaders.valueAt(i);

                        AString request;
                        request = "DESCRIBE ";
                        request.append(mSessionURL);
                        request.append(" RTSP/1.0\r\n");
                        request.append("Accept: application/sdp\r\n");
                        request.append("\r\n");

                        sp<AMessage> reply = new AMessage('desc', id());
                        mConn->sendRequest(request.c_str(), reply);
                        break;
                    }

                    if (response->mStatusCode != 200) {
                        result = UNKNOWN_ERROR;
                    } else {
                        mSessionDesc = new ASessionDescription;

                        mSessionDesc->setTo(
                                response->mContent->data(),
                                response->mContent->size());

                        if (!mSessionDesc->isValid()) {
                            result = ERROR_MALFORMED;
                        } else {
                            ssize_t i = response->mHeaders.indexOfKey("content-base");
                            if (i >= 0) {
                                mBaseURL = response->mHeaders.valueAt(i);
                            } else {
                                i = response->mHeaders.indexOfKey("content-location");
                                if (i >= 0) {
                                    mBaseURL = response->mHeaders.valueAt(i);
                                } else {
                                    mBaseURL = mSessionURL;
                                }
                            }
                            AString isLiveStream;
                            if (mSessionDesc->findAttribute(0, "a=LiveStream", &isLiveStream)) {
                                ssize_t semicolonPos = isLiveStream.find(";", 2);
                                AString valString;
                                if (semicolonPos < 0) {
                                    valString = isLiveStream;
                                } else {
                                    valString.setTo(
                                            isLiveStream,
                                            semicolonPos + 1,
                                            isLiveStream.size() - semicolonPos - 1);
                                }
                                const char* liveStream = valString.c_str();
                                uint32_t val = strtoul(liveStream, NULL, 10);
                                if (val == 1) {
                                    mIsLiveStream = true;
                                    LOGV("found live stream");
                                }
                            }

                            CHECK_GT(mSessionDesc->countTracks(), 1u);
                            setupTrack(1);
                        }
                    }
                }

                if (result != OK) {
                    sp<AMessage> reply = new AMessage('disc', id());
                    mConn->disconnect(reply);
                }
                break;
            }

            case 'setu':
            {
                size_t index;
                CHECK(msg->findSize("index", &index));

                TrackInfo *track = NULL;
                size_t trackIndex;
                if (msg->findSize("track-index", &trackIndex)) {
                    track = &mTracks.editItemAt(trackIndex);
                }

                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("SETUP(%d) completed with result %d (%s)",
                     index, result, strerror(-result));

                if (result == OK) {
                    CHECK(track != NULL);

                    sp<RefBase> obj;
                    CHECK(msg->findObject("response", &obj));
                    sp<ARTSPResponse> response =
                        static_cast<ARTSPResponse *>(obj.get());

                    if (response->mStatusCode != 200) {
                        result = UNKNOWN_ERROR;
                    } else {
                        ssize_t i = response->mHeaders.indexOfKey("session");
                        CHECK_GE(i, 0);

                        mSessionID = response->mHeaders.valueAt(i);
                        if (!mOptionsSent) {
                            i = mSessionID.find("=");
                            if (i >= 0) {
                                AString timeout = mSessionID;
                                i = mSessionID.find(";");
                                timeout.erase(0, i + 1);
                                if (sscanf(timeout.c_str(), "timeout=%lld",
                                    &mTimeoutValue) == 1) {
                                    //Send a option message just before timeout (90%), convert
                                    //time to micro seconds
                                    mTimeoutValue = (mTimeoutValue * 90) / 100;
                                    mTimeoutValue = mTimeoutValue * 1000000;
                                } else {
                                    //Something wrong with the result from sscanf, use default value
                                    mTimeoutValue = kOptionsTimeoutUs;
                                }
                            } else {
                                mTimeoutValue = kOptionsTimeoutUs;
                            }
                            sp<AMessage> options = new AMessage('opt1', id());
                            options->post(mTimeoutValue);
                            mOptionsSent = true;
                        }

                        i = mSessionID.find(";");
                        if (i >= 0) {
                            // Remove options, i.e. ";timeout=90"
                            mSessionID.erase(i, mSessionID.size() - i);
                        }

                        sp<AMessage> notify = new AMessage('accu', id());
                        notify->setSize("track-index", trackIndex);

                        i = response->mHeaders.indexOfKey("transport");
                        CHECK_GE(i, 0);

                        if (!track->mUsingInterleavedTCP) {
                            AString transport = response->mHeaders.valueAt(i);

                            pokeAHole(track->mRTPSocket,
                                      track->mRTCPSocket,
                                      transport);
                        }

                        mRTPConn->addStream(
                                track->mRTPSocket, track->mRTCPSocket,
                                mSessionDesc, index,
                                notify, track->mUsingInterleavedTCP);

                        mSetupTracksSuccessful = true;
                    }
                }

                if (result != OK) {
                    if (track) {
                        if (!track->mUsingInterleavedTCP) {
                            close(track->mRTPSocket);
                            close(track->mRTCPSocket);
                        }

                        mTracks.removeItemsAt(trackIndex);
                    }
                }

                ++index;
                if (index < mSessionDesc->countTracks()) {
                    setupTrack(index);
                } else if (mSetupTracksSuccessful) {
                    AString request = "PLAY ";
                    request.append(mSessionURL);
                    request.append(" RTSP/1.0\r\n");

                    request.append("Session: ");
                    request.append(mSessionID);
                    request.append("\r\n");

                    if (mStartTimeUs > 0) {
                        request.append(
                            StringPrintf(
                                "Range: npt=%lld-\r\n", mStartTimeUs / 1000000ll));
                    }

                    request.append("\r\n");

                    sp<AMessage> reply = new AMessage('play', id());
                    mConn->sendRequest(request.c_str(), reply);
                } else {
                    sp<AMessage> reply = new AMessage('disc', id());
                    mConn->disconnect(reply);
                }
                break;
            }

            case 'opt1':
            {
                if (mTimeoutValue > 0) {
                    AString request = "OPTIONS ";
                    request.append(mSessionURL);
                    request.append(" RTSP/1.0\r\n");

                    request.append("Session: ");
                    request.append(mSessionID);
                    request.append("\r\n");

                    request.append("\r\n");

                    sp<AMessage> options = new AMessage('opt2', id());
                    mConn->sendRequest(request.c_str(), options);

                    //Trigger next options message with delay
                    sp<AMessage> newOptions = new AMessage('opt1', id());
                    newOptions->post(mTimeoutValue);
                }
                break;
            }

            case 'opt2':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("OPTIONS completed with result %d (%s)", result, strerror(-result));

                break;
            }

            case 'play':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("PLAY completed with result %d (%s)",
                     result, strerror(-result));

                if (mDisconnectDoneMsg != NULL) {
                    LOGV("Disconnect detected, don't continue with setup");
                    break;
                }

                if (result == OK) {
                    sp<RefBase> obj;
                    CHECK(msg->findObject("response", &obj));
                    sp<ARTSPResponse> response =
                        static_cast<ARTSPResponse *>(obj.get());

                    if (response->mStatusCode != 200) {
                        result = UNKNOWN_ERROR;
                    } else {
                        parsePlayResponse(response);

                        sp<AMessage> timeout = new AMessage('tiou', id());
                        timeout->post(kStartupTimeoutUs);
                    }
                }

                if (result != OK) {
                    sp<AMessage> reply = new AMessage('disc', id());
                    mConn->disconnect(reply);
                }

                break;
            }

            case 'abor':
            {
                for (size_t i = 0; i < mTracks.size(); ++i) {
                    TrackInfo *info = &mTracks.editItemAt(i);

                    info->mPacketSource->signalEOS(ERROR_END_OF_STREAM);

                    if (!info->mUsingInterleavedTCP) {
                        mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);

                        close(info->mRTPSocket);
                        close(info->mRTCPSocket);
                    }
                }
                mTracks.clear();
                mSetupTracksSuccessful = false;
                mSeekPending = false;
                mFirstAccessUnit = true;
                mFirstAccessUnitNTP = 0;
                mTimeoutValue = 0;
                mNumAccessUnitsReceived = 0;
                mReceivedFirstRTCPPacket = false;
                mReceivedFirstRTPPacket = false;
                mSeekable = false;
                mPausing = false;
                mOptionsSent = false;

                sp<AMessage> reply = new AMessage('tear', id());

                int32_t reconnect;
                if (msg->findInt32("reconnect", &reconnect) && reconnect) {
                    reply->setInt32("reconnect", true);
                }

                AString request;
                request = "TEARDOWN ";

                // XXX should use aggregate url from SDP here...
                request.append(mSessionURL);
                request.append(" RTSP/1.0\r\n");

                request.append("Session: ");
                request.append(mSessionID);
                request.append("\r\n");

                request.append("\r\n");

                mConn->sendRequest(request.c_str(), reply);
                break;
            }

            case 'tear':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("TEARDOWN completed with result %d (%s)",
                     result, strerror(-result));

                sp<AMessage> reply = new AMessage('disc', id());

                int32_t reconnect;
                if (msg->findInt32("reconnect", &reconnect) && reconnect) {
                    reply->setInt32("reconnect", true);
                }

                mConn->disconnect(reply);
                break;
            }

            case 'quit':
            {
                if (mDisconnectDoneMsg != NULL) {
                    mDisconnectDoneMsg->setInt32("result", UNKNOWN_ERROR);
                    mDisconnectDoneMsg->post();
                    mDisconnectDoneMsg = NULL;
                }
                if (mConnectDoneMsg != NULL) {
                    mConnectDoneMsg->setInt32("result", UNKNOWN_ERROR);
                    mConnectDoneMsg->post();
                    mConnectDoneMsg = NULL;
                }
                break;
            }

            case 'chek':
            {
                int32_t generation;
                CHECK(msg->findInt32("generation", &generation));
                if (generation != mCheckGeneration) {
                    // This is an outdated message. Ignore.
                    break;
                }

                if (mNumAccessUnitsReceived == 0) {
                    LOGI("stream ended? aborting.");
                    (new AMessage('abor', id()))->post();
                    break;
                }

                mNumAccessUnitsReceived = 0;
                msg->post(kAccessUnitTimeoutUs);
                break;
            }

            case 'accu':
            {
                int32_t first;
                if (msg->findInt32("first-rtcp", &first)) {
                    mReceivedFirstRTCPPacket = true;
                    break;
                }

                if (msg->findInt32("first-rtp", &first)) {
                    mReceivedFirstRTPPacket = true;
                    break;
                }

                ++mNumAccessUnitsReceived;
                postAccessUnitTimeoutCheck();

                size_t trackIndex;
                CHECK(msg->findSize("track-index", &trackIndex));

                if (trackIndex >= mTracks.size()) {
                    LOGV("late packets ignored.");
                    break;
                }

                TrackInfo *track = &mTracks.editItemAt(trackIndex);

                int32_t eos;
                if (msg->findInt32("eos", &eos)) {
                    LOGI("received BYE on track index %d", trackIndex);
                    track->mPacketSource->signalEOS(ERROR_END_OF_STREAM);
                    return;
                }

                sp<RefBase> obj;
                CHECK(msg->findObject("access-unit", &obj));

                sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get());

                uint32_t seqNum = (uint32_t)accessUnit->int32Data();

                if (mSeekPending) {
                    LOGV("we're seeking, dropping stale packet.");
                    break;
                }

                if (seqNum < track->mFirstSeqNumInSegment) {
                    LOGV("dropping stale access-unit (%d < %d)",
                         seqNum, track->mFirstSeqNumInSegment);
                    break;
                }

                uint64_t ntpTime;
                CHECK(accessUnit->meta()->findInt64(
                            "ntp-time", (int64_t *)&ntpTime));

                uint32_t rtpTime;
                CHECK(accessUnit->meta()->findInt32(
                            "rtp-time", (int32_t *)&rtpTime));

                if (mIsLiveStream && !track->mHasSetLiveMapping) {
                    track->mPacketSource->setNormalPlayTimeMapping(
                        rtpTime, 0);
                    track->mHasSetLiveMapping = true;
                }

                if (track->mNewSegment) {
                    track->mNewSegment = false;

                    LOGV("first segment unit ntpTime=0x%016llx rtpTime=%u seq=%d",
                         ntpTime, rtpTime, seqNum);
                }

                if (mFirstAccessUnit) {
                    if (mConnectDoneMsg != NULL) {
                        mConnectDoneMsg->setInt32("result", OK);
                        mConnectDoneMsg->post();
                        mConnectDoneMsg = NULL;
                    }

                    mFirstAccessUnit = false;
                    mFirstAccessUnitNTP = ntpTime;
                }

                if (ntpTime >= mFirstAccessUnitNTP) {
                    ntpTime -= mFirstAccessUnitNTP;
                } else {
                    ntpTime = 0;
                }

                int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));

                accessUnit->meta()->setInt64("timeUs", timeUs);

#if 0
                int32_t damaged;
                if (accessUnit->meta()->findInt32("damaged", &damaged)
                        && damaged != 0) {
                    LOGI("ignoring damaged AU");
                } else
#endif
                {
                    TrackInfo *track = &mTracks.editItemAt(trackIndex);
                    track->mPacketSource->queueAccessUnit(accessUnit);
                }
                break;
            }

            case 'paus':
            {
                mCheckPending = true;
                ++mCheckGeneration;
                mPausing = true;

                AString request = "PAUSE ";
                request.append(mSessionURL);
                request.append(" RTSP/1.0\r\n");

                request.append("Session: ");
                request.append(mSessionID);
                request.append("\r\n");

                request.append("\r\n");

                sp<AMessage> reply = new AMessage('pau2', id());
                mConn->sendRequest(request.c_str(), reply);
                break;
            }

            case 'pau2':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("PAUSE completed with result %d (%s)",
                     result, strerror(-result));
                break;
            }

            case 'resu':
            {
                if (mPausing && mSeekPending) {
                    // If seeking, send PLAY from see1 instead
                    sp<AMessage> reply = new AMessage('see1', id());
                    reply->setInt64("time", mSeekPos);
                    reply->post();
                    break;
                }
                if (!mPausing) {
                    // Dont send PLAY if we have not paused
                    break;
                }
                AString request = "PLAY ";
                request.append(mSessionURL);
                request.append(" RTSP/1.0\r\n");

                request.append("Session: ");
                request.append(mSessionID);
                request.append("\r\n");

                request.append("\r\n");

                sp<AMessage> reply = new AMessage('res2', id());
                mConn->sendRequest(request.c_str(), reply);
                break;
            }

            case 'res2':
            {
                int32_t result;
                CHECK(msg->findInt32("result", &result));

                LOGI("PLAY completed with result %d (%s)",
                     result, strerror(-result));

                mCheckPending = false;
                postAccessUnitTimeoutCheck();

                if (result == OK) {
                    sp<RefBase> obj;
                    CHECK(msg->findObject("response", &obj));
                    sp<ARTSPResponse> response =
                        static_cast<ARTSPResponse *>(obj.get());

                    if (response->mStatusCode != 200) {
                        result = UNKNOWN_ERROR;
                    } else {
                        parsePlayResponse(response);
                    }
                }

                if (result != OK) {
                    LOGE("resume failed, aborting.");
                    (new AMessage('abor', id()))->post();
                }

                mPausing = false;
                break;
            }

            case 'seek':
            {
                int64_t timeUs;
                CHECK(msg->findInt64("time", &timeUs));

                sp<AMessage> doneMsg;
                CHECK(msg->findMessage("doneMsg", &doneMsg));

                if (mSeekPending) {
                    doneMsg->post();
                    break;
                }

                if (!mSeekable) {
                    LOGW("This is a live stream, ignoring seek request.");
                    doneMsg->post();
                    break;
                }

                //store new seek position and wait 0.5s
                mSeekPos = timeUs;

                LOGI("Storing new seek position and send message with delay: %lld",
                    mSeekPos);

                for (size_t i = 0; i < mTracks.size(); ++i) {
                    mTracks.editItemAt(i).mPacketSource->setSeekPlayTimeMapping(mSeekPos);
                }

                //send a new message with delay to allow more seek updates
                sp<AMessage> msg = new AMessage('seekwait', id());
                msg->setInt64("time", timeUs);
                msg->post(kDelaySeek);

                //send done message
                doneMsg->post();
                break;
            }

            case 'seekwait':
            {
                int64_t timeUs;
                CHECK(msg->findInt64("time", &timeUs));

                if (timeUs != mSeekPos) {
                    break;
                }

                // No new seek position have arrived, start the seek operation
                LOGI("Seek is triggered");

                mSeekPending = true;

                // Disable the access unit timeout until we resumed
                // playback again.
                mCheckPending = true;
                ++mCheckGeneration;

                sp<AMessage> reply = new AMessage('see1', id());
                reply->setInt64("time", timeUs);

                if (mPausing) {
                    // PAUSE already sent
                    LOGI("Pause already sent");
                    reply->post();
                    break;
                }
                AString request = "PAUSE ";
                request.append(mSessionURL);
                request.append(" RTSP/1.0\r\n");

                request.append("Session: ");
                request.append(mSessionID);
                request.append("\r\n");

                request.append("\r\n");

                mConn->sendRequest(request.c_str(), reply);
                break;
            }

            case 'see1':
            {
                // Session is paused now.
                for (size_t i = 0; i < mTracks.size(); ++i) {
                    mTracks.editItemAt(i).mPacketSource->flushQueue();
                }

                //Wait for new RTP and NTP time information
                mRTPConn->waitOnNewSRInfo();

                int64_t timeUs;
                CHECK(msg->findInt64("time", &timeUs));

                AString request = "PLAY ";
                request.append(mSessionURL);
                request.append(" RTSP/1.0\r\n");

                request.append("Session: ");
                request.append(mSessionID);
                request.append("\r\n");

                request.append(
                        StringPrintf(
                            "Range: npt=%lld-\r\n", timeUs / 1000000ll));

                request.append("\r\n");

                sp<AMessage> reply = new AMessage('see2', id());
                mConn->sendRequest(request.c_str(), reply);
                break;
            }

            case 'see2':
            {
                int32_t result;
                if (!mSeekPending) {
                    // If mSeekPending is false here, we have already hit abor, break
                    break;
                }
                CHECK(msg->findInt32("result", &result));

                LOGI("PLAY completed with result %d (%s)",
                     result, strerror(-result));

                mCheckPending = false;
                postAccessUnitTimeoutCheck();

                if (result == OK) {
                    sp<RefBase> obj;
                    CHECK(msg->findObject("response", &obj));
                    sp<ARTSPResponse> response =
                        static_cast<ARTSPResponse *>(obj.get());

                    if (response->mStatusCode != 200) {
                        result = UNKNOWN_ERROR;
                    } else {
                        parsePlayResponse(response);

                        LOGI("seek completed.");
                    }
                }

                if (result != OK) {
                    LOGE("seek failed, aborting.");
                    (new AMessage('abor', id()))->post();
                }

                mPausing = false;
                mSeekPending = false;
                break;
            }

            case 'biny':
            {
                sp<RefBase> obj;
                CHECK(msg->findObject("buffer", &obj));
                sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get());

                int32_t index;
                CHECK(buffer->meta()->findInt32("index", &index));

                mRTPConn->injectPacket(index, buffer);
                break;
            }

            case 'tiou':
            {
                if (!mReceivedFirstRTCPPacket) {
                    if (mTryFakeRTCP) {
                        LOGW("Never received any data, disconnecting.");
                        (new AMessage('abor', id()))->post();
                    } else if (mTryTCPInterleaving && mReceivedFirstRTPPacket) {
                        LOGW("We received RTP packets but no RTCP packets, "
                             "using fake timestamps.");

                        mTryFakeRTCP = true;

                        mReceivedFirstRTCPPacket = true;
                        mRTPConn->fakeTimestamps();
                        for (size_t i = 0; i < mTracks.size(); ++i) {
                            mTracks.editItemAt(i).mPacketSource
                                ->setUseFakeTimeStamps(true);
                        }
                    } else {
                        LOGW("Never received any data, switching transports.");

                        mTryTCPInterleaving = true;

                        sp<AMessage> msg = new AMessage('abor', id());
                        msg->setInt32("reconnect", true);
                        msg->post();
                    }
                }
                break;
            }

            default:
                TRESPASS();
                break;
        }
    }

    void postAccessUnitTimeoutCheck() {
        if (mCheckPending) {
            return;
        }

        mCheckPending = true;
        sp<AMessage> check = new AMessage('chek', id());
        check->setInt32("generation", mCheckGeneration);
        check->post(kAccessUnitTimeoutUs);
    }

    static void SplitString(
            const AString &s, const char *separator, List<AString> *items) {
        items->clear();
        size_t start = 0;
        while (start < s.size()) {
            ssize_t offset = s.find(separator, start);

            if (offset < 0) {
                items->push_back(AString(s, start, s.size() - start));
                break;
            }

            items->push_back(AString(s, start, offset - start));
            start = offset + strlen(separator);
        }
    }

    void parsePlayResponse(const sp<ARTSPResponse> &response) {
        mSeekable = false;

        ssize_t i = response->mHeaders.indexOfKey("range");
        if (i < 0) {
            // Server doesn't even tell use what range it is going to
            // play, therefore we won't support seeking.
            return;
        }

        AString range = response->mHeaders.valueAt(i);
        LOGV("Range: %s", range.c_str());

        AString val;
        CHECK(GetAttribute(range.c_str(), "npt", &val));
        float npt1, npt2;

        if (val == "now-" || val == "0-") {
            // This is a live stream and therefore not seekable.
            mIsLiveStream = true;
            return;
        } else {
            sscanf(val.c_str(), "%f-%f", &npt1, &npt2);
        }

        i = response->mHeaders.indexOfKey("rtp-info");
        CHECK_GE(i, 0);

        AString rtpInfo = response->mHeaders.valueAt(i);
        List<AString> streamInfos;
        SplitString(rtpInfo, ",", &streamInfos);

        int n = 1;
        for (List<AString>::iterator it = streamInfos.begin();
             it != streamInfos.end(); ++it) {
            (*it).trim();
            LOGV("streamInfo[%d] = %s", n, (*it).c_str());

            CHECK(GetAttribute((*it).c_str(), "url", &val));

            size_t trackIndex = 0;
            while (trackIndex < mTracks.size()) {
                size_t startpos = 0;
                if (mTracks.editItemAt(trackIndex).mURL.size() >= val.size()) {
                    startpos = mTracks.editItemAt(trackIndex).mURL.size() - val.size();
                }
                // Use AString::find in order to allow the url in the RTP-Info to be a
                // truncated variant (example: "url=trackID=1") of the complete SETUP url
                if (mTracks.editItemAt(trackIndex).mURL.find(val.c_str(), startpos) == -1) {
                    ++trackIndex;
                } else {
                    // Found track
                    break;
                }
            }
            CHECK_LT(trackIndex, mTracks.size());

            CHECK(GetAttribute((*it).c_str(), "seq", &val));

            char *end;
            unsigned long seq = strtoul(val.c_str(), &end, 10);

            TrackInfo *info = &mTracks.editItemAt(trackIndex);
            info->mFirstSeqNumInSegment = seq;
            info->mNewSegment = true;

            CHECK(GetAttribute((*it).c_str(), "rtptime", &val));

            uint32_t rtpTime = strtoul(val.c_str(), &end, 10);

            LOGV("track #%d: rtpTime=%u <=> npt=%.2f", n, rtpTime, npt1);

            info->mPacketSource->setNormalPlayTimeMapping(
                    rtpTime, (int64_t)(npt1 * 1E6));

            ++n;
        }

        mSeekable = true;
    }

    sp<APacketSource> getPacketSource(size_t index) {
        CHECK_GE(index, 0u);
        CHECK_LT(index, mTracks.size());

        return mTracks.editItemAt(index).mPacketSource;
    }

    size_t countTracks() const {
        return mTracks.size();
    }

private:
    sp<ALooper> mLooper;
    sp<ALooper> mNetLooper;
    sp<ARTSPConnection> mConn;
    sp<ARTPConnection> mRTPConn;
    sp<ASessionDescription> mSessionDesc;
    AString mOriginalSessionURL;  // This one still has user:pass@
    AString mSessionURL;
    AString mBaseURL;
    AString mSessionID;
    bool mSetupTracksSuccessful;
    bool mSeekPending;
    bool mFirstAccessUnit;
    uint64_t mFirstAccessUnitNTP;
    int64_t mNumAccessUnitsReceived;
    int64_t mSeekPos;
    int64_t mTimeoutValue;
    bool mCheckPending;
    int32_t mCheckGeneration;
    bool mTryTCPInterleaving;
    bool mTryFakeRTCP;
    bool mReceivedFirstRTCPPacket;
    bool mReceivedFirstRTPPacket;
    bool mSeekable;
    bool mPausing;
    bool mIsLiveStream;
    bool mOptionsSent;
    int64_t mStartTimeUs;

    struct TrackInfo {
        AString mURL;
        int mRTPSocket;
        int mRTCPSocket;
        bool mUsingInterleavedTCP;
        uint32_t mFirstSeqNumInSegment;
        bool mNewSegment;
        bool mHasSetLiveMapping;

        sp<APacketSource> mPacketSource;
    };
    Vector<TrackInfo> mTracks;

    sp<AMessage> mDisconnectDoneMsg;
    sp<AMessage> mConnectDoneMsg;

    void setupTrack(size_t index) {
        sp<APacketSource> source =
            new APacketSource(mSessionDesc, index);

        if (source->initCheck() != OK) {
            LOGW("Unsupported format. Ignoring track #%d.", index);

            sp<AMessage> reply = new AMessage('setu', id());
            reply->setSize("index", index);
            reply->setInt32("result", ERROR_UNSUPPORTED);
            reply->post();
            return;
        }

        AString url;
        CHECK(mSessionDesc->findAttribute(index, "a=control", &url));

        AString trackURL;
        CHECK(MakeURL(mBaseURL.c_str(), url.c_str(), &trackURL));

        mTracks.push(TrackInfo());
        TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1);
        info->mURL = trackURL;
        info->mPacketSource = source;
        info->mUsingInterleavedTCP = false;
        info->mFirstSeqNumInSegment = 0;
        info->mNewSegment = true;
        info->mHasSetLiveMapping = false;

        LOGV("track #%d URL=%s", mTracks.size(), trackURL.c_str());

        AString request = "SETUP ";
        request.append(trackURL);
        request.append(" RTSP/1.0\r\n");

        if (mTryTCPInterleaving) {
            size_t interleaveIndex = 2 * (mTracks.size() - 1);
            info->mUsingInterleavedTCP = true;
            info->mRTPSocket = interleaveIndex;
            info->mRTCPSocket = interleaveIndex + 1;

            request.append("Transport: RTP/AVP/TCP;interleaved=");
            request.append(interleaveIndex);
            request.append("-");
            request.append(interleaveIndex + 1);
        } else {
            unsigned rtpPort;
            ARTPConnection::MakePortPair(
                    &info->mRTPSocket, &info->mRTCPSocket, &rtpPort);

            request.append("Transport: RTP/AVP/UDP;unicast;client_port=");
            request.append(rtpPort);
            request.append("-");
            request.append(rtpPort + 1);
        }

        request.append("\r\n");

        if (index > 1) {
            request.append("Session: ");
            request.append(mSessionID);
            request.append("\r\n");
        }

        request.append("\r\n");

        sp<AMessage> reply = new AMessage('setu', id());
        reply->setSize("index", index);
        reply->setSize("track-index", mTracks.size() - 1);
        mConn->sendRequest(request.c_str(), reply);
    }

    static bool MakeURL(const char *baseURL, const char *url, AString *out) {
        out->clear();

        if (strncasecmp("rtsp://", baseURL, 7)) {
            // Base URL must be absolute
            return false;
        }

        if (!strncasecmp("rtsp://", url, 7)) {
            // "url" is already an absolute URL, ignore base URL.
            out->setTo(url);
            return true;
        }

        size_t n = strlen(baseURL);
        if (baseURL[n - 1] == '/') {
            out->setTo(baseURL);
            out->append(url);
        } else {
            char *slashPos = strrchr(baseURL, '/');

            if (slashPos > &baseURL[6]) {
                out->setTo(baseURL, slashPos - baseURL);
            } else {
                out->setTo(baseURL);
            }

            out->append("/");
            out->append(url);
        }

        return true;
    }

    DISALLOW_EVIL_CONSTRUCTORS(MyHandler);
};

}  // namespace android

#endif  // MY_HANDLER_H_
