/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//#define USE_LOG SLAndroidLogLevel_Verbose

#include "android_SfPlayer.h"

#include <stdio.h>
#include "SLES/OpenSLES.h"
#include "SLES/OpenSLES_Android.h"
#include "sllog.h"
#include <stdlib.h>

#ifdef _DEBUG_AUDIO_TESTS
// defines used for automated audio quality tests
#define MONITOR_AUDIO_TARGET "/sdcard/playbackqual.raw"
#define MONITOR_AUDIO_PLAY_PROP "system.media.sles-decode-dump"
#include <cutils/properties.h> // for property_get
#endif
namespace android {

SfPlayer::SfPlayer(AudioPlayback_Parameters *app)
    : mAudioTrack(NULL),
      mFlags(0),
      mBitrate(-1),
      mNumChannels(1),
      mSampleRateHz(0),
      mTimeDelta(-1),
      mDurationUsec(-1),
      mCacheStatus(kStatusEmpty),
      mSeekTimeMsec(0),
      mLastDecodedPositionUs(-1),
      mCacheFill(0),
      mLastNotifiedCacheFill(0),
      mCacheFillNotifThreshold(100),
      mDataLocatorType(kDataLocatorNone),
      mNotifyClient(NULL),
      mNotifyUser(NULL),
      mDecodeBuffer(NULL) {

      mRenderLooper = new android::ALooper();

      mPlaybackParams.sessionId = app->sessionId;
      mPlaybackParams.streamType = app->streamType;
      mPlaybackParams.trackcb = app->trackcb;
      mPlaybackParams.trackcbUser = app->trackcbUser;
#ifdef _DEBUG_AUDIO_TESTS
      mMonitorAudioFp = NULL; // automated tests
#endif
}


SfPlayer::~SfPlayer() {
    SL_LOGV("SfPlayer::~SfPlayer()");

    mRenderLooper->stop();
    mRenderLooper->unregisterHandler(this->id());
    mRenderLooper.clear();

    if (mAudioTrack != NULL) {
        mAudioTrack->stop();
        delete mAudioTrack;
        mAudioTrack = NULL;
    }

    if (mAudioSource != NULL) {
        {
            // don't even think about stopping the media source without releasing the decode buffer
            Mutex::Autolock _l(mDecodeBufferLock);
            if (NULL != mDecodeBuffer) {
                mDecodeBuffer->release();
                mDecodeBuffer = NULL;
            }
        }
        mAudioSource->stop();
    }

    resetDataLocator();
}

void SfPlayer::armLooper() {
    mRenderLooper->registerHandler(this);
    mRenderLooper->start(false /*runOnCallingThread*/, false /*canCallJava*/,
            ANDROID_PRIORITY_AUDIO);
}


void SfPlayer::setNotifListener(const notif_client_t cbf, void* notifUser) {
    mNotifyClient = cbf;
    mNotifyUser = notifUser;
}


void SfPlayer::notifyPrepared(status_t prepareRes) {
    sp<AMessage> msg = new AMessage(kWhatNotif, id());
    msg->setInt32(EVENT_PREPARED, (int32_t)prepareRes);
    notify(msg, true /*async*/);
}


void SfPlayer::notifyStatus() {
    sp<AMessage> msg = new AMessage(kWhatNotif, id());
    msg->setInt32(EVENT_PREFETCHSTATUSCHANGE, (int32_t)mCacheStatus);
    notify(msg, true /*async*/);
}


void SfPlayer::notifyCacheFill() {
    sp<AMessage> msg = new AMessage(kWhatNotif, id());
    mLastNotifiedCacheFill = mCacheFill;
    msg->setInt32(EVENT_PREFETCHFILLLEVELUPDATE, (int32_t)mLastNotifiedCacheFill);
    notify(msg, true /*async*/);
}


void SfPlayer::notify(const sp<AMessage> &msg, bool async) {
    if (async) {
        msg->post();
    } else {
        onNotify(msg);
    }
}


void SfPlayer::setDataSource(const char *uri) {
    resetDataLocator();

    size_t len = strlen((const char *) uri);
    char* newUri = (char*) malloc(len + 1);
    if (NULL == newUri) {
        // mem issue
        SL_LOGE("SfPlayer::setDataSource: not enough memory to allocator URI string");
        return;
    }
    memcpy(newUri, uri, len + 1);
    mDataLocator.uri = newUri;

    mDataLocatorType = kDataLocatorUri;
}

void SfPlayer::setDataSource(const int fd, const int64_t offset, const int64_t length) {
    resetDataLocator();

    mDataLocator.fdi.fd = fd;

    struct stat sb;
    int ret = fstat(fd, &sb);
    if (ret != 0) {
        // sockets are not supported
        SL_LOGE("SfPlayer::setDataSource: fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
        return;
    }

    if (offset >= sb.st_size) {
        SL_LOGE("SfPlayer::setDataSource: invalid offset");
        return;
    }
    mDataLocator.fdi.offset = offset;

    if (SFPLAYER_FD_FIND_FILE_SIZE == length) {
        mDataLocator.fdi.length = sb.st_size;
    } else if (offset + length > sb.st_size) {
        mDataLocator.fdi.length = sb.st_size - offset;
    } else {
        mDataLocator.fdi.length = length;
    }

    mDataLocatorType = kDataLocatorFd;
}

void SfPlayer::prepare() {
    //SL_LOGV("SfPlayer::prepare()");
    sp<AMessage> msg = new AMessage(kWhatPrepare, id());
    msg->post();
}


void SfPlayer::onPrepare(const sp<AMessage> &msg) {
    SL_LOGV("SfPlayer::onPrepare");
    sp<DataSource> dataSource;

    switch (mDataLocatorType) {

        case kDataLocatorNone:
            SL_LOGE("SfPlayer::onPrepare: no data locator set");
            notifyPrepared(MEDIA_ERROR_BASE);
            break;

        case kDataLocatorUri:
            if (!strncasecmp(mDataLocator.uri, "http://", 7)) {
                sp<NuHTTPDataSource> http = new NuHTTPDataSource;
                if (http->connect(mDataLocator.uri) == OK) {
                    dataSource =
                        new NuCachedSource2(
                                new ThrottledSource(
                                        http, 50 * 1024 /* bytes/sec */));
                }
            } else {
                dataSource = DataSource::CreateFromURI(mDataLocator.uri);
            }
            break;

        case kDataLocatorFd: {
            dataSource = new FileSource(
                    mDataLocator.fdi.fd, mDataLocator.fdi.offset, mDataLocator.fdi.length);
            status_t err = dataSource->initCheck();
            if (err != OK) {
                notifyPrepared(err);
                return;
            }
            }
            break;

        default:
            TRESPASS();
    }

    if (dataSource == NULL) {
        SL_LOGE("SfPlayer::onPrepare: Could not create data source.");
        notifyPrepared(ERROR_UNSUPPORTED);
        return;
    }

    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
    if (extractor == NULL) {
        SL_LOGE("SfPlayer::onPrepare: Could not instantiate extractor.");
        notifyPrepared(ERROR_UNSUPPORTED);
        return;
    }

    ssize_t audioTrackIndex = -1;
    bool isRawAudio = false;
    for (size_t i = 0; i < extractor->countTracks(); ++i) {
        sp<MetaData> meta = extractor->getTrackMetaData(i);

        const char *mime;
        CHECK(meta->findCString(kKeyMIMEType, &mime));

        if (!strncasecmp("audio/", mime, 6)) {
            audioTrackIndex = i;

            if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_RAW, mime)) {
                isRawAudio = true;
            }
            break;
        }
    }

    if (audioTrackIndex < 0) {
        SL_LOGE("SfPlayer::onPrepare: Could not find a supported audio track.");
        notifyPrepared(ERROR_UNSUPPORTED);
        return;
    }

    sp<MediaSource> source = extractor->getTrack(audioTrackIndex);
    sp<MetaData> meta = source->getFormat();

    off_t size;
    int64_t durationUs;
    if (dataSource->getSize(&size) == OK
            && meta->findInt64(kKeyDuration, &durationUs)) {
        mBitrate = size * 8000000ll / durationUs;  // in bits/sec
        mDurationUsec = durationUs;
    } else {
        mBitrate = -1;
        mDurationUsec = -1;
    }

    if (!isRawAudio) {
        OMXClient client;
        CHECK_EQ(client.connect(), (status_t)OK);

        source = OMXCodec::Create(
                client.interface(), meta, false /* createEncoder */,
                source);

        if (source == NULL) {
            SL_LOGE("SfPlayer::onPrepare: Could not instantiate decoder.");
            notifyPrepared(ERROR_UNSUPPORTED);
            return;
        }

        meta = source->getFormat();
    }

    if (source->start() != OK) {
        SL_LOGE("SfPlayer::onPrepare: Failed to start source/decoder.");
        notifyPrepared(MEDIA_ERROR_BASE);
        return;
    }

    mDataSource = dataSource;
    mAudioSource = source;

    CHECK(meta->findInt32(kKeyChannelCount, &mNumChannels));
    CHECK(meta->findInt32(kKeySampleRate, &mSampleRateHz));

    if (!wantPrefetch()) {
        SL_LOGV("SfPlayer::onPrepare: no need to prefetch");
        // doesn't need prefetching, notify good to go
        mCacheStatus = kStatusHigh;
        mCacheFill = 1000;
        notifyStatus();
        notifyCacheFill();
    }

    // at this point we have enough information about the source to create its associated AudioTrack
    assert(NULL == mAudioTrack);
    mAudioTrack = new android::AudioTrack(
            mPlaybackParams.streamType,                          // streamType
            mSampleRateHz,                                       // sampleRate
            android::AudioSystem::PCM_16_BIT,                    // format
            mNumChannels == 1 ?     //channel mask
                    android::AudioSystem::CHANNEL_OUT_MONO :
                    android::AudioSystem::CHANNEL_OUT_STEREO,
            0,                                                   // frameCount (here min)
            0,                                                   // flags
            mPlaybackParams.trackcb,                             // callback
            mPlaybackParams.trackcbUser,                         // user
            0,                                                   // notificationFrame
            mPlaybackParams.sessionId
        );

    //SL_LOGV("SfPlayer::onPrepare: end");
    notifyPrepared(SFPLAYER_SUCCESS);

}


bool SfPlayer::wantPrefetch() {
    return (mDataSource->flags() & DataSource::kWantsPrefetching);
}


void SfPlayer::startPrefetch_async() {
    SL_LOGV("SfPlayer::startPrefetch_async()");
    if (wantPrefetch()) {
        //SL_LOGV("SfPlayer::startPrefetch_async(): sending check cache msg");

        mFlags |= kFlagPreparing;
        mFlags |= kFlagBuffering;

        (new AMessage(kWhatCheckCache, id()))->post();
    }
}


void SfPlayer::play() {
    SL_LOGV("SfPlayer::play");

    (new AMessage(kWhatPlay, id()))->post();
    (new AMessage(kWhatDecode, id()))->post();
}


void SfPlayer::stop() {
    SL_LOGV("SfPlayer::stop");

    if (NULL != mAudioTrack) {
        mAudioTrack->stop();
    }

    (new AMessage(kWhatPause, id()))->post();

    // after a stop, playback should resume from the start.
    seek(0);
}

void SfPlayer::pause() {
    SL_LOGV("SfPlayer::pause");
    if (NULL == mAudioTrack) {
        return;
    }
    (new AMessage(kWhatPause, id()))->post();
    mAudioTrack->pause();
}

void SfPlayer::seek(int64_t timeMsec) {
    SL_LOGV("SfPlayer::seek %lld", timeMsec);
    sp<AMessage> msg = new AMessage(kWhatSeek, id());
    msg->setInt64("seek", timeMsec);
    msg->post();
}


void SfPlayer::loop(bool loop) {
    sp<AMessage> msg = new AMessage(kWhatLoop, id());
    msg->setInt32("loop", (int32_t)loop);
    msg->post();
}


uint32_t SfPlayer::getPositionMsec() {
    Mutex::Autolock _l(mSeekLock);
    if (mFlags & kFlagSeeking) {
        return (uint32_t) mSeekTimeMsec;
    } else {
        if (mLastDecodedPositionUs < 0) {
            return 0;
        } else {
            return (uint32_t) (mLastDecodedPositionUs / 1000);
        }
    }
}


int64_t SfPlayer::getPositionUsec() {
    Mutex::Autolock _l(mSeekLock);
    if (mFlags & kFlagSeeking) {
        return mSeekTimeMsec * 1000;
    } else {
        if (mLastDecodedPositionUs < 0) {
            return 0;
        } else {
            return mLastDecodedPositionUs;
        }
    }
}

/**
 * called from message loop
 */
void SfPlayer::reachedEndOfStream() {
    SL_LOGV("SfPlayer::reachedEndOfStream");
    if (mFlags & kFlagPlaying) {
        // async notification of end of stream reached during playback
        sp<AMessage> msg = new AMessage(kWhatNotif, id());
        msg->setInt32(EVENT_ENDOFSTREAM, 1);
        notify(msg, true /*async*/);
    }
    if (mFlags & kFlagLooping) {
        seek(0);
        // kick-off decoding again
        (new AMessage(kWhatDecode, id()))->post();
    }
}

/**
 * called from message loop
 */
void SfPlayer::updatePlaybackParamsFromSource() {
    if (mAudioSource != 0) {
        sp<MetaData> meta = mAudioSource->getFormat();

        SL_LOGV("old sample rate = %d", mSampleRateHz);
        CHECK(meta->findInt32(kKeyChannelCount, &mNumChannels));
        CHECK(meta->findInt32(kKeySampleRate, &mSampleRateHz));
        SL_LOGV("new sample rate = %d", mSampleRateHz);

        // the AudioTrack currently used by the AudioPlayer will be deleted by AudioPlayer itself
        // SfPlayer never deletes the AudioTrack it creates and uses.
        if (NULL != mAudioTrack) {
            mAudioTrack->stop();
            delete mAudioTrack;
            mAudioTrack = NULL;
        }
        mAudioTrack = new android::AudioTrack(
                mPlaybackParams.streamType,                          // streamType
                mSampleRateHz,                                       // sampleRate
                android::AudioSystem::PCM_16_BIT,                    // format
                mNumChannels == 1 ?     //channel mask
                        android::AudioSystem::CHANNEL_OUT_MONO :
                        android::AudioSystem::CHANNEL_OUT_STEREO,
                0,                                                   // frameCount (here min)
                0,                                                   // flags
                mPlaybackParams.trackcb,                             // callback
                mPlaybackParams.trackcbUser,                         // user
                0,                                                   // notificationFrame
                mPlaybackParams.sessionId
        );
        if (mFlags & kFlagPlaying) {
            mAudioTrack->start();
        }

        // notify the AudioPlayer synchronously there's a new AudioTrack to use and configure
        sp<AMessage> msg = new AMessage(kWhatNotif, id());
        msg->setInt32(EVENT_NEW_AUDIOTRACK, 0/*data field unused*/);
        notify(msg, false /*async*/);
    }
}


/**
 * Message handlers
 */

void SfPlayer::onPlay() {
    SL_LOGV("SfPlayer::onPlay");
#ifdef _DEBUG_AUDIO_TESTS
    // Automated tests: Open file for Intercepting pcm audio for quality validation
    char value[PROPERTY_VALUE_MAX];
    if (property_get(MONITOR_AUDIO_PLAY_PROP, value, NULL) &&
           (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
        mMonitorAudioFp = fopen(MONITOR_AUDIO_TARGET, "w");
        if (mMonitorAudioFp == NULL) { LOGE("error opening %s", MONITOR_AUDIO_TARGET); }
        else { LOGE("recording to %s", MONITOR_AUDIO_TARGET); }
    }
#endif
    mFlags |= kFlagPlaying;

    if (NULL != mAudioTrack) {
        mAudioTrack->start();
    }
}


void SfPlayer::onPause() {
    SL_LOGV("SfPlayer::onPause");
    mFlags &= ~kFlagPlaying;
#ifdef _DEBUG_AUDIO_TESTS
    // Automated tests: close intercept file
    if (mMonitorAudioFp != NULL) {
        fclose(mMonitorAudioFp);
    }
    mMonitorAudioFp = NULL;
#endif
}


void SfPlayer::onSeek(const sp<AMessage> &msg) {
    SL_LOGV("SfPlayer::onSeek");
    int64_t timeMsec;
    CHECK(msg->findInt64("seek", &timeMsec));

    Mutex::Autolock _l(mSeekLock);
    mFlags |= kFlagSeeking;
    mSeekTimeMsec = timeMsec;
    mTimeDelta = -1;
    mLastDecodedPositionUs = -1;
}


void SfPlayer::onLoop(const sp<AMessage> &msg) {
    //SL_LOGV("SfPlayer::onLoop");
    int32_t loop;
    CHECK(msg->findInt32("loop", &loop));

    if (loop) {
        //SL_LOGV("SfPlayer::onLoop start looping");
        mFlags |= kFlagLooping;
    } else {
        //SL_LOGV("SfPlayer::onLoop stop looping");
        mFlags &= ~kFlagLooping;
    }
}


void SfPlayer::onDecode() {
    //SL_LOGV("SfPlayer::onDecode");
    bool eos;
    if (mDataSource == 0) {
        // application set play state to paused which failed, then set play state to playing
        return;
    }
    if ((mDataSource->flags() & DataSource::kWantsPrefetching)
            && (getCacheRemaining(&eos) == kStatusLow)
            && !eos) {
        SL_LOGV("buffering more.");

        if (mFlags & kFlagPlaying) {
            mAudioTrack->pause();
        }
        mFlags |= kFlagBuffering;
        (new AMessage(kWhatCheckCache, id()))->post(100000);
        return;
    }

    if (!(mFlags & (kFlagPlaying | kFlagBuffering | kFlagPreparing))) {
        // don't decode if we're not buffering, prefetching or playing
        //SL_LOGV("don't decode: not buffering, prefetching or playing");
        return;
    }

    status_t err;
    MediaSource::ReadOptions readOptions;
    if (mFlags & kFlagSeeking) {
        readOptions.setSeekTo(mSeekTimeMsec * 1000);
    }

    {
        Mutex::Autolock _l(mDecodeBufferLock);
        if (NULL != mDecodeBuffer) {
            // the current decoded buffer hasn't been rendered, drop it
            mDecodeBuffer->release();
            mDecodeBuffer = NULL;
        }
        err = mAudioSource->read(&mDecodeBuffer, &readOptions);
        if (err == OK) {
            CHECK(mDecodeBuffer->meta_data()->findInt64(kKeyTime, &mLastDecodedPositionUs));
        }
    }

    {
        Mutex::Autolock _l(mSeekLock);
        if (mFlags & kFlagSeeking) {
            mFlags &= ~kFlagSeeking;
        }
    }

    if (err != OK) {
        bool continueDecoding = false;
        switch(err) {
            case ERROR_END_OF_STREAM:
                // handle notification and looping at end of stream
                if (0 < mDurationUsec) {
                    mLastDecodedPositionUs = mDurationUsec;
                }
                reachedEndOfStream();
                break;
            case INFO_FORMAT_CHANGED:
                SL_LOGI("MediaSource::read encountered INFO_FORMAT_CHANGED");
                // reconfigure output
                updatePlaybackParamsFromSource();
                continueDecoding = true;
                break;
            case INFO_DISCONTINUITY:
                SL_LOGI("MediaSource::read encountered INFO_DISCONTINUITY");
                continueDecoding = true;
                break;
            default:
                SL_LOGE("MediaSource::read returned error %d", err);
                break;
        }
        if (continueDecoding) {
            if (NULL == mDecodeBuffer) {
                (new AMessage(kWhatDecode, id()))->post();
                return;
            }
        } else {
            return;
        }
    }

    // render

    sp<AMessage> msg = new AMessage(kWhatRender, id());

    if (mTimeDelta < 0) {
        mTimeDelta = ALooper::GetNowUs() - mLastDecodedPositionUs;
    }

    int64_t delayUs = mLastDecodedPositionUs + mTimeDelta - ALooper::GetNowUs()
            - RENDER_SAFETY_DELAY_US; // negative delays are ignored


    if ((NULL != mAudioTrack) && (mAudioTrack->getSampleRate() > mSampleRateHz)) {
        // we're speeding up playback, feed data faster
        // FIXME not the right formula, delays need to be evaluated differently
        delayUs = RENDER_SAFETY_DELAY_US;
        //SL_LOGV("delayUs=%lld new", delayUs);
    }

    // FIXME clicks can be observed if solely relying on delayUs, this is a safe compromise
    msg->post(delayUs > RENDER_SAFETY_DELAY_US ? RENDER_SAFETY_DELAY_US : delayUs);
    //msg->post(delayUs); // negative delays are ignored
    //SL_LOGV("timeUs=%lld, mTimeDelta=%lld, delayUs=%lld",
    //        mLastDecodedPositionUs, mTimeDelta, delayUs);
}


void SfPlayer::onRender(const sp<AMessage> &msg) {
    //SL_LOGV("SfPlayer::onRender");

    Mutex::Autolock _l(mDecodeBufferLock);

    if (NULL == mDecodeBuffer) {
        // nothing to render, move along
        //SL_LOGV("SfPlayer::onRender NULL buffer, exiting");
        return;
    }

    if (mFlags & kFlagPlaying) {
        assert(NULL != mAudioTrack);
        mAudioTrack->write( (const uint8_t *)mDecodeBuffer->data() + mDecodeBuffer->range_offset(),
                mDecodeBuffer->range_length());
        (new AMessage(kWhatDecode, id()))->post();
#ifdef _DEBUG_AUDIO_TESTS
        // Automated tests: Intercept PCM data and write to file for later validations
        if (mMonitorAudioFp != NULL) {
            fwrite((const uint8_t *)mDecodeBuffer->data() + mDecodeBuffer->range_offset(),
                    mDecodeBuffer->range_length(), 1, mMonitorAudioFp);
        }
#endif
    }
    mDecodeBuffer->release();
    mDecodeBuffer = NULL;

}


void SfPlayer::onCheckCache(const sp<AMessage> &msg) {
    //SL_LOGV("SfPlayer::onCheckCache");
    bool eos;
    CacheStatus status = getCacheRemaining(&eos);

    if (eos || status == kStatusHigh
            || ((mFlags & kFlagPreparing) && (status >= kStatusEnough))) {
        if (mFlags & kFlagPlaying) {
            assert(NULL != mAudioTrack);
            mAudioTrack->start();
        }
        mFlags &= ~kFlagBuffering;

        SL_LOGV("SfPlayer::onCheckCache: buffering done.");

        if (mFlags & kFlagPreparing) {
            //SL_LOGV("SfPlayer::onCheckCache: preparation done.");
            mFlags &= ~kFlagPreparing;
        }

        mTimeDelta = -1;
        if (mFlags & kFlagPlaying) {
            (new AMessage(kWhatDecode, id()))->post();
        }
        return;
    }

    msg->post(100000);
}

void SfPlayer::onNotify(const sp<AMessage> &msg) {
    if (NULL == mNotifyClient) {
        return;
    }
    int32_t val;
    if (msg->findInt32(EVENT_PREFETCHSTATUSCHANGE, &val)) {
        SL_LOGV("\tSfPlayer notifying %s = %d", EVENT_PREFETCHSTATUSCHANGE, val);
        mNotifyClient(kEventPrefetchStatusChange, val, mNotifyUser);
    }
    if (msg->findInt32(EVENT_PREFETCHFILLLEVELUPDATE, &val)) {
        SL_LOGV("\tSfPlayer notifying %s = %d", EVENT_PREFETCHFILLLEVELUPDATE, val);
        mNotifyClient(kEventPrefetchFillLevelUpdate, val, mNotifyUser);
    }
    if (msg->findInt32(EVENT_ENDOFSTREAM, &val)) {
        SL_LOGV("\tSfPlayer notifying %s = %d", EVENT_ENDOFSTREAM, val);
        mNotifyClient(kEventEndOfStream, val, mNotifyUser);
    }

    if (msg->findInt32(EVENT_PREPARED, &val)) {
        SL_LOGV("\tSfPlayer notifying %s = %d", EVENT_PREPARED, val);
        mNotifyClient(kEventPrepared, val, mNotifyUser);
    }

    if (msg->findInt32(EVENT_NEW_AUDIOTRACK, &val)) {
        SL_LOGV("\tSfPlayer notifying %s", EVENT_NEW_AUDIOTRACK);
        mNotifyClient(kEventNewAudioTrack, val, mNotifyUser);
    }
}

SfPlayer::CacheStatus SfPlayer::getCacheRemaining(bool *eos) {
    sp<NuCachedSource2> cachedSource =
        static_cast<NuCachedSource2 *>(mDataSource.get());

    CacheStatus oldStatus = mCacheStatus;

    size_t dataRemaining = cachedSource->approxDataRemaining(eos);

    CHECK_GE(mBitrate, 0);

    int64_t dataRemainingUs = dataRemaining * 8000000ll / mBitrate;

    //SL_LOGV("SfPlayer::getCacheRemaining: approx %.2f secs remaining (eos=%d)",
    //       dataRemainingUs / 1E6, *eos);

    if (*eos) {
        // data is buffered up to the end of the stream, it can't get any better than this
        mCacheStatus = kStatusHigh;
        mCacheFill = 1000;

    } else {
        if (mDurationUsec > 0) {
            // known duration:

            //   fill level is ratio of how much has been played + how much is
            //   cached, divided by total duration
            uint32_t currentPositionUsec = getPositionUsec();
            mCacheFill = (int16_t) ((1000.0
                    * (double)(currentPositionUsec + dataRemainingUs) / mDurationUsec));
            //SL_LOGV("cacheFill = %d", mCacheFill);

            //   cache status is evaluated against duration thresholds
            if (dataRemainingUs > DURATION_CACHED_HIGH_US) {
                mCacheStatus = kStatusHigh;
                //LOGV("high");
            } else if (dataRemainingUs > DURATION_CACHED_MED_US) {
                //LOGV("enough");
                mCacheStatus = kStatusEnough;
            } else if (dataRemainingUs < DURATION_CACHED_LOW_US) {
                //LOGV("low");
                mCacheStatus = kStatusLow;
            } else {
                mCacheStatus = kStatusIntermediate;
            }

        } else {
            // unknown duration:

            //   cache status is evaluated against cache amount thresholds
            //   (no duration so we don't have the bitrate either, could be derived from format?)
            if (dataRemaining > SIZE_CACHED_HIGH_BYTES) {
                mCacheStatus = kStatusHigh;
            } else if (dataRemaining > SIZE_CACHED_MED_BYTES) {
                mCacheStatus = kStatusEnough;
            } else if (dataRemaining < SIZE_CACHED_LOW_BYTES) {
                mCacheStatus = kStatusLow;
            } else {
                mCacheStatus = kStatusIntermediate;
            }
        }

    }

    if (oldStatus != mCacheStatus) {
        notifyStatus();
    }

    if (abs(mCacheFill - mLastNotifiedCacheFill) > mCacheFillNotifThreshold) {
        notifyCacheFill();
    }

    return mCacheStatus;
}


/*
 * post-condition: mDataLocatorType == kDataLocatorNone
 *
 */
void SfPlayer::resetDataLocator() {
    if (kDataLocatorUri == mDataLocatorType) {
        if (NULL != mDataLocator.uri) {
            free(mDataLocator.uri);
            mDataLocator.uri = NULL;
        }
    }
    mDataLocatorType = kDataLocatorNone;
}


void SfPlayer::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case kWhatPrepare:
            onPrepare(msg);
            break;

        case kWhatDecode:
            onDecode();
            break;

        case kWhatRender:
            onRender(msg);
            break;

        case kWhatCheckCache:
            onCheckCache(msg);
            break;

        case kWhatNotif:
            onNotify(msg);
            break;

        case kWhatPlay:
            onPlay();
            break;

        case kWhatPause:
            onPause();
            break;

        case kWhatSeek:
            onSeek(msg);
            break;

        case kWhatLoop:
            onLoop(msg);
            break;

        default:
            TRESPASS();
    }
}

}  // namespace android