/*
 * 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.
 */

#include "sles_allinclusive.h"
#include "utils/RefBase.h"
#include "android_prompts.h"

template class android::KeyedVector<SLuint32, android::AudioEffect* > ;

#define KEY_STREAM_TYPE_PARAMSIZE  sizeof(SLint32)

//-----------------------------------------------------------------------------
int android_getMinFrameCount(uint32_t sampleRate) {
    int afSampleRate;
    if (android::AudioSystem::getOutputSamplingRate(&afSampleRate,
            ANDROID_DEFAULT_OUTPUT_STREAM_TYPE) != android::NO_ERROR) {
        return ANDROID_DEFAULT_AUDIOTRACK_BUFFER_SIZE;
    }
    int afFrameCount;
    if (android::AudioSystem::getOutputFrameCount(&afFrameCount,
            ANDROID_DEFAULT_OUTPUT_STREAM_TYPE) != android::NO_ERROR) {
        return ANDROID_DEFAULT_AUDIOTRACK_BUFFER_SIZE;
    }
    uint32_t afLatency;
    if (android::AudioSystem::getOutputLatency(&afLatency,
            ANDROID_DEFAULT_OUTPUT_STREAM_TYPE) != android::NO_ERROR) {
        return ANDROID_DEFAULT_AUDIOTRACK_BUFFER_SIZE;
    }
    // minimum nb of buffers to cover output latency, given the size of each hardware audio buffer
    uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate);
    if (minBufCount < 2) minBufCount = 2;
    // minimum number of frames to cover output latency at the sample rate of the content
    return (afFrameCount*sampleRate*minBufCount)/afSampleRate;
}


//-----------------------------------------------------------------------------
#define LEFT_CHANNEL_MASK  0x1 << 0
#define RIGHT_CHANNEL_MASK 0x1 << 1

static void android_audioPlayer_updateStereoVolume(CAudioPlayer* ap) {
    float leftVol = 1.0f, rightVol = 1.0f;

    if (NULL == ap->mAudioTrack) {
        return;
    }
    // should not be used when muted
    if (SL_BOOLEAN_TRUE == ap->mMute) {
        return;
    }

    int channelCount = ap->mNumChannels;

    // mute has priority over solo
    int leftAudibilityFactor = 1, rightAudibilityFactor = 1;

    if (channelCount >= STEREO_CHANNELS) {
        if (ap->mMuteMask & LEFT_CHANNEL_MASK) {
            // left muted
            leftAudibilityFactor = 0;
        } else {
            // left not muted
            if (ap->mSoloMask & LEFT_CHANNEL_MASK) {
                // left soloed
                leftAudibilityFactor = 1;
            } else {
                // left not soloed
                if (ap->mSoloMask & RIGHT_CHANNEL_MASK) {
                    // right solo silences left
                    leftAudibilityFactor = 0;
                } else {
                    // left and right are not soloed, and left is not muted
                    leftAudibilityFactor = 1;
                }
            }
        }

        if (ap->mMuteMask & RIGHT_CHANNEL_MASK) {
            // right muted
            rightAudibilityFactor = 0;
        } else {
            // right not muted
            if (ap->mSoloMask & RIGHT_CHANNEL_MASK) {
                // right soloed
                rightAudibilityFactor = 1;
            } else {
                // right not soloed
                if (ap->mSoloMask & LEFT_CHANNEL_MASK) {
                    // left solo silences right
                    rightAudibilityFactor = 0;
                } else {
                    // left and right are not soloed, and right is not muted
                    rightAudibilityFactor = 1;
                }
            }
        }
    }

    // compute amplification as the combination of volume level and stereo position
    //   amplification from volume level
    ap->mAmplFromVolLevel = sles_to_android_amplification(ap->mVolume.mLevel);
    //   amplification from direct level (changed in SLEffectSendtItf and SLAndroidEffectSendItf)
    leftVol  *= ap->mAmplFromVolLevel * ap->mAmplFromDirectLevel;
    rightVol *= ap->mAmplFromVolLevel * ap->mAmplFromDirectLevel;

    // amplification from stereo position
    if (ap->mVolume.mEnableStereoPosition) {
        // panning law depends on number of channels of content: stereo panning vs 2ch. balance
        if(1 == channelCount) {
            // stereo panning
            double theta = (1000+ap->mVolume.mStereoPosition)*M_PI_4/1000.0f; // 0 <= theta <= Pi/2
            ap->mAmplFromStereoPos[0] = cos(theta);
            ap->mAmplFromStereoPos[1] = sin(theta);
        } else {
            // stereo balance
            if (ap->mVolume.mStereoPosition > 0) {
                ap->mAmplFromStereoPos[0] = (1000-ap->mVolume.mStereoPosition)/1000.0f;
                ap->mAmplFromStereoPos[1] = 1.0f;
            } else {
                ap->mAmplFromStereoPos[0] = 1.0f;
                ap->mAmplFromStereoPos[1] = (1000+ap->mVolume.mStereoPosition)/1000.0f;
            }
        }
        leftVol  *= ap->mAmplFromStereoPos[0];
        rightVol *= ap->mAmplFromStereoPos[1];
    }

    ap->mAudioTrack->setVolume(leftVol * leftAudibilityFactor, rightVol * rightAudibilityFactor);

    // changes in the AudioPlayer volume must be reflected in the send level:
    //  in SLEffectSendItf or in SLAndroidEffectSendItf?
    // FIXME replace interface test by an internal API once we have one.
    if (NULL != ap->mEffectSend.mItf) {
        for (unsigned int i=0 ; i<AUX_MAX ; i++) {
            if (ap->mEffectSend.mEnableLevels[i].mEnable) {
                android_fxSend_setSendLevel(ap,
                        ap->mEffectSend.mEnableLevels[i].mSendLevel + ap->mVolume.mLevel);
                // there's a single aux bus on Android, so we can stop looking once the first
                // aux effect is found.
                break;
            }
        }
    } else if (NULL != ap->mAndroidEffectSend.mItf) {
        android_fxSend_setSendLevel(ap, ap->mAndroidEffectSend.mSendLevel + ap->mVolume.mLevel);
    }
}

//-----------------------------------------------------------------------------
void audioTrack_handleMarker_lockPlay(CAudioPlayer* ap) {
    //SL_LOGV("received event EVENT_MARKER from AudioTrack");
    slPlayCallback callback = NULL;
    void* callbackPContext = NULL;

    interface_lock_shared(&ap->mPlay);
    callback = ap->mPlay.mCallback;
    callbackPContext = ap->mPlay.mContext;
    interface_unlock_shared(&ap->mPlay);

    if (NULL != callback) {
        // getting this event implies SL_PLAYEVENT_HEADATMARKER was set in the event mask
        (*callback)(&ap->mPlay.mItf, callbackPContext, SL_PLAYEVENT_HEADATMARKER);
    }
}

//-----------------------------------------------------------------------------
void audioTrack_handleNewPos_lockPlay(CAudioPlayer* ap) {
    //SL_LOGV("received event EVENT_NEW_POS from AudioTrack");
    slPlayCallback callback = NULL;
    void* callbackPContext = NULL;

    interface_lock_shared(&ap->mPlay);
    callback = ap->mPlay.mCallback;
    callbackPContext = ap->mPlay.mContext;
    interface_unlock_shared(&ap->mPlay);

    if (NULL != callback) {
        // getting this event implies SL_PLAYEVENT_HEADATNEWPOS was set in the event mask
        (*callback)(&ap->mPlay.mItf, callbackPContext, SL_PLAYEVENT_HEADATNEWPOS);
    }
}


//-----------------------------------------------------------------------------
void audioTrack_handleUnderrun_lockPlay(CAudioPlayer* ap) {
    slPlayCallback callback = NULL;
    void* callbackPContext = NULL;

    interface_lock_shared(&ap->mPlay);
    callback = ap->mPlay.mCallback;
    callbackPContext = ap->mPlay.mContext;
    bool headStalled = (ap->mPlay.mEventFlags & SL_PLAYEVENT_HEADSTALLED) != 0;
    interface_unlock_shared(&ap->mPlay);

    if ((NULL != callback) && headStalled) {
        (*callback)(&ap->mPlay.mItf, callbackPContext, SL_PLAYEVENT_HEADSTALLED);
    }
}


//-----------------------------------------------------------------------------
/**
 * post-condition: play state of AudioPlayer is SL_PLAYSTATE_PAUSED if setPlayStateToPaused is true
 *
 * note: a conditional flag, setPlayStateToPaused, is used here to specify whether the play state
 *       needs to be changed when the player reaches the end of the content to play. This is
 *       relative to what the specification describes for buffer queues vs the
 *       SL_PLAYEVENT_HEADATEND event. In the OpenSL ES specification 1.0.1:
 *        - section 8.12 SLBufferQueueItf states "In the case of starvation due to insufficient
 *          buffers in the queue, the playing of audio data stops. The player remains in the
 *          SL_PLAYSTATE_PLAYING state."
 *        - section 9.2.31 SL_PLAYEVENT states "SL_PLAYEVENT_HEADATEND Playback head is at the end
 *          of the current content and the player has paused."
 */
void audioPlayer_dispatch_headAtEnd_lockPlay(CAudioPlayer *ap, bool setPlayStateToPaused,
        bool needToLock) {
    //SL_LOGV("ap=%p, setPlayStateToPaused=%d, needToLock=%d", ap, setPlayStateToPaused,
    //        needToLock);
    slPlayCallback playCallback = NULL;
    void * playContext = NULL;
    // SLPlayItf callback or no callback?
    if (needToLock) {
        interface_lock_exclusive(&ap->mPlay);
    }
    if (ap->mPlay.mEventFlags & SL_PLAYEVENT_HEADATEND) {
        playCallback = ap->mPlay.mCallback;
        playContext = ap->mPlay.mContext;
    }
    if (setPlayStateToPaused) {
        ap->mPlay.mState = SL_PLAYSTATE_PAUSED;
    }
    if (needToLock) {
        interface_unlock_exclusive(&ap->mPlay);
    }
    // callback with no lock held
    if (NULL != playCallback) {
        (*playCallback)(&ap->mPlay.mItf, playContext, SL_PLAYEVENT_HEADATEND);
    }

}


//-----------------------------------------------------------------------------
/**
 * pre-condition: AudioPlayer has SLPrefetchStatusItf initialized
 * post-condition:
 *  - ap->mPrefetchStatus.mStatus == status
 *  - the prefetch status callback, if any, has been notified if a change occurred
 *
 */
void audioPlayer_dispatch_prefetchStatus_lockPrefetch(CAudioPlayer *ap, SLuint32 status,
        bool needToLock) {
    slPrefetchCallback prefetchCallback = NULL;
    void * prefetchContext = NULL;

    if (needToLock) {
        interface_lock_exclusive(&ap->mPrefetchStatus);
    }
    // status change?
    if (ap->mPrefetchStatus.mStatus != status) {
        ap->mPrefetchStatus.mStatus = status;
        // callback or no callback?
        if (ap->mPrefetchStatus.mCallbackEventsMask & SL_PREFETCHEVENT_STATUSCHANGE) {
            prefetchCallback = ap->mPrefetchStatus.mCallback;
            prefetchContext  = ap->mPrefetchStatus.mContext;
        }
    }
    if (needToLock) {
        interface_unlock_exclusive(&ap->mPrefetchStatus);
    }

    // callback with no lock held
    if (NULL != prefetchCallback) {
        (*prefetchCallback)(&ap->mPrefetchStatus.mItf, prefetchContext, status);
    }
}


//-----------------------------------------------------------------------------
SLresult audioPlayer_setStreamType(CAudioPlayer* ap, SLint32 type) {
    SLresult result = SL_RESULT_SUCCESS;
    SL_LOGV("type %ld", type);

    int newStreamType = ANDROID_DEFAULT_OUTPUT_STREAM_TYPE;
    switch(type) {
    case SL_ANDROID_STREAM_VOICE:
        newStreamType = android::AudioSystem::VOICE_CALL;
        break;
    case SL_ANDROID_STREAM_SYSTEM:
        newStreamType = android::AudioSystem::SYSTEM;
        break;
    case SL_ANDROID_STREAM_RING:
        newStreamType = android::AudioSystem::RING;
        break;
    case SL_ANDROID_STREAM_MEDIA:
        newStreamType = android::AudioSystem::MUSIC;
        break;
    case SL_ANDROID_STREAM_ALARM:
        newStreamType = android::AudioSystem::ALARM;
        break;
    case SL_ANDROID_STREAM_NOTIFICATION:
        newStreamType = android::AudioSystem::NOTIFICATION;
        break;
    default:
        SL_LOGE(ERROR_PLAYERSTREAMTYPE_SET_UNKNOWN_TYPE);
        result = SL_RESULT_PARAMETER_INVALID;
        break;
    }

    // stream type needs to be set before the object is realized
    // (ap->mAudioTrack is supposed to be NULL until then)
    if (SL_OBJECT_STATE_UNREALIZED != ap->mObject.mState) {
        SL_LOGE(ERROR_PLAYERSTREAMTYPE_REALIZED);
        result = SL_RESULT_PRECONDITIONS_VIOLATED;
    } else {
        ap->mStreamType = newStreamType;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult audioPlayer_getStreamType(CAudioPlayer* ap, SLint32 *pType) {
    SLresult result = SL_RESULT_SUCCESS;

    switch(ap->mStreamType) {
    case android::AudioSystem::VOICE_CALL:
        *pType = SL_ANDROID_STREAM_VOICE;
        break;
    case android::AudioSystem::SYSTEM:
        *pType = SL_ANDROID_STREAM_SYSTEM;
        break;
    case android::AudioSystem::RING:
        *pType = SL_ANDROID_STREAM_RING;
        break;
    case android::AudioSystem::DEFAULT:
    case android::AudioSystem::MUSIC:
        *pType = SL_ANDROID_STREAM_MEDIA;
        break;
    case android::AudioSystem::ALARM:
        *pType = SL_ANDROID_STREAM_ALARM;
        break;
    case android::AudioSystem::NOTIFICATION:
        *pType = SL_ANDROID_STREAM_NOTIFICATION;
        break;
    default:
        result = SL_RESULT_INTERNAL_ERROR;
        *pType = SL_ANDROID_STREAM_MEDIA;
        break;
    }

    return result;
}


//-----------------------------------------------------------------------------
#ifndef USE_BACKPORT
static void sfplayer_prepare(CAudioPlayer *ap, bool lockAP) {

    if (lockAP) { object_lock_exclusive(&ap->mObject); }
    ap->mAndroidObjState = ANDROID_PREPARING;
    if (lockAP) { object_unlock_exclusive(&ap->mObject); }

    if (ap->mSfPlayer != 0) {
        ap->mSfPlayer->prepare();
    }
}
#endif

//-----------------------------------------------------------------------------
#ifndef USE_BACKPORT
// Callback associated with an SfPlayer of an SL ES AudioPlayer that gets its data
// from a URI or FD, for prepare and prefetch events
static void sfplayer_handlePrefetchEvent(const int event, const int data1, void* user) {
    if (NULL == user) {
        return;
    }

    CAudioPlayer *ap = (CAudioPlayer *)user;
    //SL_LOGV("received event %d, data %d from SfAudioPlayer", event, data1);
    switch(event) {

    case(android::SfPlayer::kEventPrepared): {
        object_lock_exclusive(&ap->mObject);

        if (SFPLAYER_SUCCESS != data1) {
            ap->mAudioTrack = NULL;
            ap->mNumChannels = 0;
            ap->mSampleRateMilliHz = 0;
            ap->mAndroidObjState = ANDROID_UNINITIALIZED;

        } else {
            ap->mAudioTrack = ap->mSfPlayer->getAudioTrack();
            ap->mNumChannels = ap->mSfPlayer->getNumChannels();
            ap->mSampleRateMilliHz = android_to_sles_sampleRate(ap->mSfPlayer->getSampleRateHz());
            ap->mSfPlayer->startPrefetch_async();

            // update the new track with the current settings
            android_audioPlayer_useEventMask(ap);
            android_audioPlayer_volumeUpdate(ap);
            android_audioPlayer_setPlayRate(ap, ap->mPlaybackRate.mRate, false /*lockAP*/);

            ap->mAndroidObjState = ANDROID_READY;
        }

        object_unlock_exclusive(&ap->mObject);
    } break;

    case(android::SfPlayer::kEventPrefetchFillLevelUpdate): {
        if (!IsInterfaceInitialized(&(ap->mObject), MPH_PREFETCHSTATUS)) {
            break;
        }
        slPrefetchCallback callback = NULL;
        void* callbackPContext = NULL;

        // SLPrefetchStatusItf callback or no callback?
        interface_lock_exclusive(&ap->mPrefetchStatus);
        if (ap->mPrefetchStatus.mCallbackEventsMask & SL_PREFETCHEVENT_FILLLEVELCHANGE) {
            callback = ap->mPrefetchStatus.mCallback;
            callbackPContext = ap->mPrefetchStatus.mContext;
        }
        ap->mPrefetchStatus.mLevel = (SLpermille)data1;
        interface_unlock_exclusive(&ap->mPrefetchStatus);

        // callback with no lock held
        if (NULL != callback) {
            (*callback)(&ap->mPrefetchStatus.mItf, callbackPContext,
                    SL_PREFETCHEVENT_FILLLEVELCHANGE);
        }
    } break;

    case(android::SfPlayer::kEventPrefetchStatusChange): {
        if (!IsInterfaceInitialized(&(ap->mObject), MPH_PREFETCHSTATUS)) {
            break;
        }
        slPrefetchCallback callback = NULL;
        void* callbackPContext = NULL;

        // SLPrefetchStatusItf callback or no callback?
        object_lock_exclusive(&ap->mObject);
        if (ap->mPrefetchStatus.mCallbackEventsMask & SL_PREFETCHEVENT_STATUSCHANGE) {
            callback = ap->mPrefetchStatus.mCallback;
            callbackPContext = ap->mPrefetchStatus.mContext;
        }
        if (data1 >= android::SfPlayer::kStatusIntermediate) {
            ap->mPrefetchStatus.mStatus = SL_PREFETCHSTATUS_SUFFICIENTDATA;
            // FIXME estimate fill level better?
            ap->mPrefetchStatus.mLevel = 1000;
            ap->mAndroidObjState = ANDROID_READY;
        } else if (data1 < android::SfPlayer::kStatusIntermediate) {
            ap->mPrefetchStatus.mStatus = SL_PREFETCHSTATUS_UNDERFLOW;
            // FIXME estimate fill level better?
            ap->mPrefetchStatus.mLevel = 0;
        }
        object_unlock_exclusive(&ap->mObject);

        // callback with no lock held
        if (NULL != callback) {
            (*callback)(&ap->mPrefetchStatus.mItf, callbackPContext, SL_PREFETCHEVENT_STATUSCHANGE);
        }
        } break;

    case(android::SfPlayer::kEventEndOfStream): {
        audioPlayer_dispatch_headAtEnd_lockPlay(ap, true /*set state to paused?*/, true);
        if ((NULL != ap->mAudioTrack) && (!ap->mSeek.mLoopEnabled)) {
            ap->mAudioTrack->stop();
        }
        } break;

    default:
        break;
    }
}
#endif


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_checkSourceSink(CAudioPlayer *pAudioPlayer)
{
    const SLDataSource *pAudioSrc = &pAudioPlayer->mDataSource.u.mSource;
    const SLDataSink *pAudioSnk = &pAudioPlayer->mDataSink.u.mSink;
    //--------------------------------------
    // Sink check:
    //     currently only OutputMix sinks are supported, regardless of the data source
    if (*(SLuint32 *)pAudioSnk->pLocator != SL_DATALOCATOR_OUTPUTMIX) {
        SL_LOGE("Cannot create audio player: data sink is not SL_DATALOCATOR_OUTPUTMIX");
        return SL_RESULT_PARAMETER_INVALID;
    }

    //--------------------------------------
    // Source check:
    SLuint32 locatorType = *(SLuint32 *)pAudioSrc->pLocator;
    SLuint32 formatType = *(SLuint32 *)pAudioSrc->pFormat;

    switch (locatorType) {
    //------------------
    //   Buffer Queues
    case SL_DATALOCATOR_BUFFERQUEUE:
    case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE:
        {
        SLDataLocator_BufferQueue *dl_bq =  (SLDataLocator_BufferQueue *) pAudioSrc->pLocator;

        // Buffer format
        switch (formatType) {
        //     currently only PCM buffer queues are supported,
        case SL_DATAFORMAT_PCM: {
            SLDataFormat_PCM *df_pcm = (SLDataFormat_PCM *) pAudioSrc->pFormat;
            switch (df_pcm->numChannels) {
            case 1:
            case 2:
                break;
            default:
                // this should have already been rejected by checkDataFormat
                SL_LOGE("Cannot create audio player: unsupported " \
                    "PCM data source with %u channels", (unsigned) df_pcm->numChannels);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }
            switch (df_pcm->samplesPerSec) {
            case SL_SAMPLINGRATE_8:
            case SL_SAMPLINGRATE_11_025:
            case SL_SAMPLINGRATE_12:
            case SL_SAMPLINGRATE_16:
            case SL_SAMPLINGRATE_22_05:
            case SL_SAMPLINGRATE_24:
            case SL_SAMPLINGRATE_32:
            case SL_SAMPLINGRATE_44_1:
            case SL_SAMPLINGRATE_48:
                break;
            case SL_SAMPLINGRATE_64:
            case SL_SAMPLINGRATE_88_2:
            case SL_SAMPLINGRATE_96:
            case SL_SAMPLINGRATE_192:
            default:
                SL_LOGE("Cannot create audio player: unsupported sample rate %u milliHz",
                    (unsigned) df_pcm->samplesPerSec);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }
            switch (df_pcm->bitsPerSample) {
            case SL_PCMSAMPLEFORMAT_FIXED_8:
                // FIXME We should support this
                //SL_LOGE("Cannot create audio player: unsupported 8-bit data");
                //return SL_RESULT_CONTENT_UNSUPPORTED;
            case SL_PCMSAMPLEFORMAT_FIXED_16:
                break;
                // others
            default:
                // this should have already been rejected by checkDataFormat
                SL_LOGE("Cannot create audio player: unsupported sample bit depth %lu",
                        (SLuint32)df_pcm->bitsPerSample);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }
            switch (df_pcm->containerSize) {
            case 8:
            case 16:
                break;
                // others
            default:
                SL_LOGE("Cannot create audio player: unsupported container size %u",
                    (unsigned) df_pcm->containerSize);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }
            switch (df_pcm->channelMask) {
                // FIXME needs work
            default:
                break;
            }
            switch (df_pcm->endianness) {
            case SL_BYTEORDER_LITTLEENDIAN:
                break;
            case SL_BYTEORDER_BIGENDIAN:
                SL_LOGE("Cannot create audio player: unsupported big-endian byte order");
                return SL_RESULT_CONTENT_UNSUPPORTED;
                // native is proposed but not yet in spec
            default:
                SL_LOGE("Cannot create audio player: unsupported byte order %u",
                    (unsigned) df_pcm->endianness);
                return SL_RESULT_CONTENT_UNSUPPORTED;
            }
            } //case SL_DATAFORMAT_PCM
            break;
        case SL_DATAFORMAT_MIME:
        case SL_DATAFORMAT_RESERVED3:
            SL_LOGE("Cannot create audio player with buffer queue data source "
                "without SL_DATAFORMAT_PCM format");
            return SL_RESULT_CONTENT_UNSUPPORTED;
        default:
            SL_LOGE("Cannot create audio player with buffer queue data source "
                "without SL_DATAFORMAT_PCM format");
            return SL_RESULT_PARAMETER_INVALID;
        } // switch (formatType)
        } // case SL_DATALOCATOR_BUFFERQUEUE or SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
        break;
    //------------------
    //   URI
    case SL_DATALOCATOR_URI:
        {
        SLDataLocator_URI *dl_uri =  (SLDataLocator_URI *) pAudioSrc->pLocator;
        if (NULL == dl_uri->URI) {
            return SL_RESULT_PARAMETER_INVALID;
        }
        // URI format
        switch (formatType) {
        case SL_DATAFORMAT_MIME:
            break;
        case SL_DATAFORMAT_PCM:
        case SL_DATAFORMAT_RESERVED3:
            SL_LOGE("Cannot create audio player with SL_DATALOCATOR_URI data source without "
                "SL_DATAFORMAT_MIME format");
            return SL_RESULT_CONTENT_UNSUPPORTED;
        } // switch (formatType)
        } // case SL_DATALOCATOR_URI
        break;
    //------------------
    //   File Descriptor
    case SL_DATALOCATOR_ANDROIDFD:
        {
        // fd is already non null
        switch (formatType) {
        case SL_DATAFORMAT_MIME:
            break;
        case SL_DATAFORMAT_PCM:
            // FIXME implement
            SL_LOGD("[ FIXME implement PCM FD data sources ]");
            break;
        case SL_DATAFORMAT_RESERVED3:
            SL_LOGE("Cannot create audio player with SL_DATALOCATOR_ANDROIDFD data source "
                "without SL_DATAFORMAT_MIME or SL_DATAFORMAT_PCM format");
            return SL_RESULT_CONTENT_UNSUPPORTED;
        } // switch (formatType)
        } // case SL_DATALOCATOR_ANDROIDFD
        break;
    //------------------
    //   Address
    case SL_DATALOCATOR_ADDRESS:
    case SL_DATALOCATOR_IODEVICE:
    case SL_DATALOCATOR_OUTPUTMIX:
    case SL_DATALOCATOR_RESERVED5:
    case SL_DATALOCATOR_MIDIBUFFERQUEUE:
    case SL_DATALOCATOR_RESERVED8:
        SL_LOGE("Cannot create audio player with data locator type 0x%x", (unsigned) locatorType);
        return SL_RESULT_CONTENT_UNSUPPORTED;
    default:
        return SL_RESULT_PARAMETER_INVALID;
    }// switch (locatorType)

    return SL_RESULT_SUCCESS;
}



//-----------------------------------------------------------------------------
static void audioTrack_callBack_uri(int event, void* user, void *info) {
    // EVENT_MORE_DATA needs to be handled with priority over the other events
    // because it will be called the most often during playback
    if (event == android::AudioTrack::EVENT_MORE_DATA) {
        //SL_LOGV("received event EVENT_MORE_DATA from AudioTrack");
        // set size to 0 to signal we're not using the callback to write more data
        android::AudioTrack::Buffer* pBuff = (android::AudioTrack::Buffer*)info;
        pBuff->size = 0;
    } else if (NULL != user) {
        switch (event) {
            case (android::AudioTrack::EVENT_MARKER) :
                audioTrack_handleMarker_lockPlay((CAudioPlayer *)user);
                break;
            case (android::AudioTrack::EVENT_NEW_POS) :
                audioTrack_handleNewPos_lockPlay((CAudioPlayer *)user);
                break;
            case (android::AudioTrack::EVENT_UNDERRUN) :
                audioTrack_handleUnderrun_lockPlay((CAudioPlayer *)user);
                break;
            default:
                SL_LOGE("Encountered unknown AudioTrack event %d for CAudioPlayer %p", event,
                        (CAudioPlayer *)user);
                break;
        }
    }
}

//-----------------------------------------------------------------------------
// Callback associated with an AudioTrack of an SL ES AudioPlayer that gets its data
// from a buffer queue.
static void audioTrack_callBack_pullFromBuffQueue(int event, void* user, void *info) {
    CAudioPlayer *ap = (CAudioPlayer *)user;
    void * callbackPContext = NULL;
    switch(event) {

    case (android::AudioTrack::EVENT_MORE_DATA) : {
        //SL_LOGV("received event EVENT_MORE_DATA from AudioTrack");
        slBufferQueueCallback callback = NULL;
        android::AudioTrack::Buffer* pBuff = (android::AudioTrack::Buffer*)info;
        // retrieve data from the buffer queue
        interface_lock_exclusive(&ap->mBufferQueue);
        if (ap->mBufferQueue.mState.count != 0) {
            //SL_LOGV("nbBuffers in queue = %lu",ap->mBufferQueue.mState.count);
            assert(ap->mBufferQueue.mFront != ap->mBufferQueue.mRear);

            BufferHeader *oldFront = ap->mBufferQueue.mFront;
            BufferHeader *newFront = &oldFront[1];

            // FIXME handle 8bit based on buffer format
            short *pSrc = (short*)((char *)oldFront->mBuffer
                    + ap->mBufferQueue.mSizeConsumed);
            if (ap->mBufferQueue.mSizeConsumed + pBuff->size < oldFront->mSize) {
                // can't consume the whole or rest of the buffer in one shot
                ap->mBufferQueue.mSizeConsumed += pBuff->size;
                // leave pBuff->size untouched
                // consume data
                // FIXME can we avoid holding the lock during the copy?
                memcpy (pBuff->i16, pSrc, pBuff->size);
            } else {
                // finish consuming the buffer or consume the buffer in one shot
                pBuff->size = oldFront->mSize - ap->mBufferQueue.mSizeConsumed;
                ap->mBufferQueue.mSizeConsumed = 0;

                if (newFront ==
                        &ap->mBufferQueue.mArray
                            [ap->mBufferQueue.mNumBuffers + 1])
                {
                    newFront = ap->mBufferQueue.mArray;
                }
                ap->mBufferQueue.mFront = newFront;

                ap->mBufferQueue.mState.count--;
                ap->mBufferQueue.mState.playIndex++;

                // consume data
                // FIXME can we avoid holding the lock during the copy?
                memcpy (pBuff->i16, pSrc, pBuff->size);

                // data has been consumed, and the buffer queue state has been updated
                // we will notify the client if applicable
                callback = ap->mBufferQueue.mCallback;
                // save callback data
                callbackPContext = ap->mBufferQueue.mContext;
            }
        } else { // empty queue
            // signal no data available
            pBuff->size = 0;

            // signal we're at the end of the content, but don't pause (see note in function)
            audioPlayer_dispatch_headAtEnd_lockPlay(ap, false /*set state to paused?*/, false);

            // signal underflow to prefetch status itf
            if (IsInterfaceInitialized(&(ap->mObject), MPH_PREFETCHSTATUS)) {
                audioPlayer_dispatch_prefetchStatus_lockPrefetch(ap, SL_PREFETCHSTATUS_UNDERFLOW,
                    false);
            }

            // stop the track so it restarts playing faster when new data is enqueued
            ap->mAudioTrack->stop();
        }
        interface_unlock_exclusive(&ap->mBufferQueue);
        // notify client
        if (NULL != callback) {
            (*callback)(&ap->mBufferQueue.mItf, callbackPContext);
        }
    }
    break;

    case (android::AudioTrack::EVENT_MARKER) :
        audioTrack_handleMarker_lockPlay(ap);
        break;

    case (android::AudioTrack::EVENT_NEW_POS) :
        audioTrack_handleNewPos_lockPlay(ap);
        break;

    case (android::AudioTrack::EVENT_UNDERRUN) :
        audioTrack_handleUnderrun_lockPlay(ap);
        break;

    default:
        // FIXME where does the notification of SL_PLAYEVENT_HEADMOVING fit?
        SL_LOGE("Encountered unknown AudioTrack event %d for CAudioPlayer %p", event,
                (CAudioPlayer *)user);
        break;
    }
}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_create(
        CAudioPlayer *pAudioPlayer) {

    const SLDataSource *pAudioSrc = &pAudioPlayer->mDataSource.u.mSource;
    const SLDataSink *pAudioSnk = &pAudioPlayer->mDataSink.u.mSink;
    SLresult result = SL_RESULT_SUCCESS;

    //--------------------------------------
    // Sink check:
    // currently only OutputMix sinks are supported
    // this has already been verified in sles_to_android_CheckAudioPlayerSourceSink
    // SLuint32 locatorType = *(SLuint32 *)pAudioSnk->pLocator;
    // if (SL_DATALOCATOR_OUTPUTMIX == locatorType) {
    // }

    //--------------------------------------
    // Source check:
    SLuint32 locatorType = *(SLuint32 *)pAudioSrc->pLocator;
    switch (locatorType) {
    //   -----------------------------------
    //   Buffer Queue to AudioTrack
    case SL_DATALOCATOR_BUFFERQUEUE:
    case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE:
        pAudioPlayer->mAndroidObjType = AUDIOTRACK_PULL;
        pAudioPlayer->mpLock = new android::Mutex();
        pAudioPlayer->mPlaybackRate.mCapabilities = SL_RATEPROP_NOPITCHCORAUDIO;
        break;
    //   -----------------------------------
    //   URI or FD to MediaPlayer
    case SL_DATALOCATOR_URI:
    case SL_DATALOCATOR_ANDROIDFD:
        pAudioPlayer->mAndroidObjType = MEDIAPLAYER;
        pAudioPlayer->mpLock = new android::Mutex();
        pAudioPlayer->mPlaybackRate.mCapabilities = SL_RATEPROP_NOPITCHCORAUDIO;
        break;
    default:
        pAudioPlayer->mAndroidObjType = INVALID_TYPE;
        pAudioPlayer->mpLock = NULL;
        pAudioPlayer->mPlaybackRate.mCapabilities = 0;
        result = SL_RESULT_PARAMETER_INVALID;
        break;
    }

    pAudioPlayer->mAndroidObjState = ANDROID_UNINITIALIZED;
    pAudioPlayer->mStreamType = ANDROID_DEFAULT_OUTPUT_STREAM_TYPE;
    pAudioPlayer->mAudioTrack = NULL;
#ifndef USE_BACKPORT
    // no longer needed, as placement new (explicit constructor) already does this
    // pAudioPlayer->mSfPlayer.clear();
#endif

#ifndef USE_BACKPORT
    pAudioPlayer->mSessionId = android::AudioSystem::newAudioSessionId();
#endif

    pAudioPlayer->mAmplFromVolLevel = 1.0f;
    pAudioPlayer->mAmplFromStereoPos[0] = 1.0f;
    pAudioPlayer->mAmplFromStereoPos[1] = 1.0f;
    pAudioPlayer->mDirectLevel = 0; // no attenuation
    pAudioPlayer->mAmplFromDirectLevel = 1.0f; // matches initial mDirectLevel value

    // initialize interface-specific fields that can be used regardless of whether the interface
    // is exposed on the AudioPlayer or not
    // (section no longer applicable, as all previous initializations were the same as the defaults)

    return result;

}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_setConfig(CAudioPlayer *ap, const SLchar *configKey,
        const void *pConfigValue, SLuint32 valueSize) {

    SLresult result = SL_RESULT_SUCCESS;

    if (NULL == ap) {
        result = SL_RESULT_INTERNAL_ERROR;
    } else if (NULL == pConfigValue) {
        SL_LOGE(ERROR_CONFIG_NULL_PARAM);
        result = SL_RESULT_PARAMETER_INVALID;

    } else if(strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_STREAM_TYPE) == 0) {

        // stream type
        if (KEY_STREAM_TYPE_PARAMSIZE > valueSize) {
            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
            result = SL_RESULT_PARAMETER_INVALID;
        } else {
            result = audioPlayer_setStreamType(ap, *(SLuint32*)pConfigValue);
        }

    } else {
        SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
        result = SL_RESULT_PARAMETER_INVALID;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_getConfig(CAudioPlayer* ap, const SLchar *configKey,
        SLuint32* pValueSize, void *pConfigValue) {

    SLresult result = SL_RESULT_SUCCESS;

    if (NULL == ap) {
        return SL_RESULT_INTERNAL_ERROR;
    } else if (NULL == pValueSize) {
        SL_LOGE(ERROR_CONFIG_NULL_PARAM);
        result = SL_RESULT_PARAMETER_INVALID;

    } else if(strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_STREAM_TYPE) == 0) {

        // stream type
        if (KEY_STREAM_TYPE_PARAMSIZE > *pValueSize) {
            SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
            result = SL_RESULT_PARAMETER_INVALID;
        } else {
            *pValueSize = KEY_STREAM_TYPE_PARAMSIZE;
            if (NULL != pConfigValue) {
                result = audioPlayer_getStreamType(ap, (SLint32*)pConfigValue);
            }
        }

    } else {
        SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
        result = SL_RESULT_PARAMETER_INVALID;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_realize(CAudioPlayer *pAudioPlayer, SLboolean async) {

    SLresult result = SL_RESULT_SUCCESS;
    SL_LOGV("Realize pAudioPlayer=%p", pAudioPlayer);

    switch (pAudioPlayer->mAndroidObjType) {
    //-----------------------------------
    // AudioTrack
    case AUDIOTRACK_PULL:
        {
        // initialize platform-specific CAudioPlayer fields

        SLDataLocator_BufferQueue *dl_bq =  (SLDataLocator_BufferQueue *)
                pAudioPlayer->mDynamicSource.mDataSource;
        SLDataFormat_PCM *df_pcm = (SLDataFormat_PCM *)
                pAudioPlayer->mDynamicSource.mDataSource->pFormat;

        uint32_t sampleRate = sles_to_android_sampleRate(df_pcm->samplesPerSec);

        pAudioPlayer->mAudioTrack = new android::AudioTrack(
                pAudioPlayer->mStreamType,                           // streamType
                sampleRate,                                          // sampleRate
                sles_to_android_sampleFormat(df_pcm->bitsPerSample), // format
                sles_to_android_channelMask(df_pcm->numChannels, df_pcm->channelMask),//channel mask
                0,                                                   // frameCount (here min)
                0,                                                   // flags
                audioTrack_callBack_pullFromBuffQueue,               // callback
                (void *) pAudioPlayer,                               // user
                0      // FIXME find appropriate frame count         // notificationFrame
#ifndef USE_BACKPORT
                , pAudioPlayer->mSessionId
#endif
                );
        android::status_t status = pAudioPlayer->mAudioTrack->initCheck();
        if (status != android::NO_ERROR) {
            SL_LOGE("AudioTrack::initCheck status %u", status);
            result = SL_RESULT_CONTENT_UNSUPPORTED;
        }

        // initialize platform-independent CAudioPlayer fields

        pAudioPlayer->mNumChannels = df_pcm->numChannels;
        pAudioPlayer->mSampleRateMilliHz = df_pcm->samplesPerSec; // Note: bad field name in SL ES

        pAudioPlayer->mAndroidObjState = ANDROID_READY;
        } break;
#ifndef USE_BACKPORT
    //-----------------------------------
    // MediaPlayer
    case MEDIAPLAYER: {
        object_lock_exclusive(&pAudioPlayer->mObject);

        pAudioPlayer->mAndroidObjState = ANDROID_UNINITIALIZED;
        pAudioPlayer->mNumChannels = 0;
        pAudioPlayer->mSampleRateMilliHz = 0;
        pAudioPlayer->mAudioTrack = NULL;

        AudioPlayback_Parameters app;
        app.sessionId = pAudioPlayer->mSessionId;
        app.streamType = pAudioPlayer->mStreamType;
        app.trackcb = audioTrack_callBack_uri;
        app.trackcbUser = (void *) pAudioPlayer;

        pAudioPlayer->mSfPlayer = new android::SfPlayer(&app);
        pAudioPlayer->mSfPlayer->setNotifListener(sfplayer_handlePrefetchEvent,
                        (void*)pAudioPlayer /*notifUSer*/);
        pAudioPlayer->mSfPlayer->armLooper();

        object_unlock_exclusive(&pAudioPlayer->mObject);

        switch (pAudioPlayer->mDataSource.mLocator.mLocatorType) {
            case SL_DATALOCATOR_URI:
                pAudioPlayer->mSfPlayer->setDataSource(
                        (const char*)pAudioPlayer->mDataSource.mLocator.mURI.URI);
                break;
            case SL_DATALOCATOR_ANDROIDFD: {
                int64_t offset = (int64_t)pAudioPlayer->mDataSource.mLocator.mFD.offset;
                pAudioPlayer->mSfPlayer->setDataSource(
                        (int)pAudioPlayer->mDataSource.mLocator.mFD.fd,
                        offset == SL_DATALOCATOR_ANDROIDFD_USE_FILE_SIZE ?
                                (int64_t)SFPLAYER_FD_FIND_FILE_SIZE : offset,
                        (int64_t)pAudioPlayer->mDataSource.mLocator.mFD.length);
                } break;
            default:
                SL_LOGE(ERROR_PLAYERREALIZE_UNKNOWN_DATASOURCE_LOCATOR);
                break;
        }

        } break;
#endif
    default:
        SL_LOGE("Unexpected object type %d", pAudioPlayer->mAndroidObjType);
        result = SL_RESULT_INTERNAL_ERROR;
        break;
    }

#ifndef USE_BACKPORT

    // proceed with effect initialization
    // initialize EQ
    // FIXME use a table of effect descriptors when adding support for more effects
    if (memcmp(SL_IID_EQUALIZER, &pAudioPlayer->mEqualizer.mEqDescriptor.type,
            sizeof(effect_uuid_t)) == 0) {
        SL_LOGV("Need to initialize EQ for AudioPlayer=%p", pAudioPlayer);
        android_eq_init(pAudioPlayer->mSessionId, &pAudioPlayer->mEqualizer);
    }
    // initialize BassBoost
    if (memcmp(SL_IID_BASSBOOST, &pAudioPlayer->mBassBoost.mBassBoostDescriptor.type,
            sizeof(effect_uuid_t)) == 0) {
        SL_LOGV("Need to initialize BassBoost for AudioPlayer=%p", pAudioPlayer);
        android_bb_init(pAudioPlayer->mSessionId, &pAudioPlayer->mBassBoost);
    }
    // initialize Virtualizer
    if (memcmp(SL_IID_VIRTUALIZER, &pAudioPlayer->mVirtualizer.mVirtualizerDescriptor.type,
               sizeof(effect_uuid_t)) == 0) {
        SL_LOGV("Need to initialize Virtualizer for AudioPlayer=%p", pAudioPlayer);
        android_virt_init(pAudioPlayer->mSessionId, &pAudioPlayer->mVirtualizer);
    }

    // initialize EffectSend
    // FIXME initialize EffectSend
#endif

    return result;
}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_destroy(CAudioPlayer *pAudioPlayer) {
    SLresult result = SL_RESULT_SUCCESS;
    SL_LOGV("android_audioPlayer_destroy(%p)", pAudioPlayer);
    switch (pAudioPlayer->mAndroidObjType) {
    //-----------------------------------
    // AudioTrack
    case AUDIOTRACK_PULL:
        break;
#ifndef USE_BACKPORT
    //-----------------------------------
    // MediaPlayer
    case MEDIAPLAYER:
        // FIXME might no longer be needed since we call explicit destructor
        if (pAudioPlayer->mSfPlayer != 0) {
            pAudioPlayer->mSfPlayer.clear();
        }
        break;
#endif
    default:
        SL_LOGE("Unexpected object type %d", pAudioPlayer->mAndroidObjType);
        result = SL_RESULT_INTERNAL_ERROR;
        break;
    }

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

    // FIXME might not be needed
    pAudioPlayer->mAndroidObjType = INVALID_TYPE;

    // explicit destructor
    pAudioPlayer->mSfPlayer.~sp();

    if (pAudioPlayer->mpLock != NULL) {
        delete pAudioPlayer->mpLock;
        pAudioPlayer->mpLock = NULL;
    }

    return result;
}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_setPlayRate(CAudioPlayer *ap, SLpermille rate, bool lockAP) {
    SLresult result = SL_RESULT_SUCCESS;
    uint32_t contentRate = 0;
    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
    case MEDIAPLAYER: {
        // get the content sample rate
        if (lockAP) { object_lock_shared(&ap->mObject); }
        uint32_t contentRate = sles_to_android_sampleRate(ap->mSampleRateMilliHz);
        if (lockAP) { object_unlock_shared(&ap->mObject); }
        // apply the SL ES playback rate on the AudioTrack as a factor of its content sample rate
        if (ap->mAudioTrack != NULL) {
            ap->mAudioTrack->setSampleRate(contentRate * (rate/1000.0f));
        }
        }
        break;

    default:
        SL_LOGE("Unexpected object type %d", ap->mAndroidObjType);
        result = SL_RESULT_INTERNAL_ERROR;
        break;
    }
    return result;
}


//-----------------------------------------------------------------------------
// called with no lock held
SLresult android_audioPlayer_setPlaybackRateBehavior(CAudioPlayer *ap,
        SLuint32 constraints) {
    SLresult result = SL_RESULT_SUCCESS;
    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
    case MEDIAPLAYER:
        if (constraints != (constraints & SL_RATEPROP_NOPITCHCORAUDIO)) {
            result = SL_RESULT_FEATURE_UNSUPPORTED;
        }
        break;
    default:
        SL_LOGE("Unexpected object type %d", ap->mAndroidObjType);
        result = SL_RESULT_INTERNAL_ERROR;
        break;
    }
    return result;
}


//-----------------------------------------------------------------------------
// called with no lock held
SLresult android_audioPlayer_getCapabilitiesOfRate(CAudioPlayer *ap,
        SLuint32 *pCapabilities) {
    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
    case MEDIAPLAYER:
        *pCapabilities = SL_RATEPROP_NOPITCHCORAUDIO;
        break;
    default:
        *pCapabilities = 0;
        break;
    }
    return SL_RESULT_SUCCESS;
}


//-----------------------------------------------------------------------------
void android_audioPlayer_setPlayState(CAudioPlayer *ap, bool lockAP) {

    if (lockAP) { object_lock_shared(&ap->mObject); }
    SLuint32 playState = ap->mPlay.mState;
    AndroidObject_state objState = ap->mAndroidObjState;
    if (lockAP) { object_unlock_shared(&ap->mObject); }

    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
        switch (playState) {
        case SL_PLAYSTATE_STOPPED:
            SL_LOGV("setting AudioPlayer to SL_PLAYSTATE_STOPPED");
            if (NULL != ap->mAudioTrack) {
                ap->mAudioTrack->stop();
            }
            break;
        case SL_PLAYSTATE_PAUSED:
            SL_LOGV("setting AudioPlayer to SL_PLAYSTATE_PAUSED");
            if (NULL != ap->mAudioTrack) {
                ap->mAudioTrack->pause();
            }
            break;
        case SL_PLAYSTATE_PLAYING:
            SL_LOGV("setting AudioPlayer to SL_PLAYSTATE_PLAYING");
            if (NULL != ap->mAudioTrack) {
                ap->mAudioTrack->start();
            }
            break;
        default:
            // checked by caller, should not happen
            break;
        }
        break;
#ifndef USE_BACKPORT
    case MEDIAPLAYER:
        switch (playState) {
        case SL_PLAYSTATE_STOPPED: {
            SL_LOGV("setting AudioPlayer to SL_PLAYSTATE_STOPPED");
            if (ap->mSfPlayer != 0) {
                ap->mSfPlayer->stop();
            }
            } break;
        case SL_PLAYSTATE_PAUSED: {
            SL_LOGV("setting AudioPlayer to SL_PLAYSTATE_PAUSED");
            switch(objState) {
                case(ANDROID_UNINITIALIZED):
                    sfplayer_prepare(ap, lockAP);
                    break;
                case(ANDROID_PREPARING):
                    break;
                case(ANDROID_READY):
                    if (ap->mSfPlayer != 0) {
                        ap->mSfPlayer->pause();
                    }
                    break;
                default:
                    break;
            }
            } break;
        case SL_PLAYSTATE_PLAYING: {
            SL_LOGV("setting AudioPlayer to SL_PLAYSTATE_PLAYING");
            switch(objState) {
                case(ANDROID_UNINITIALIZED):
                    sfplayer_prepare(ap, lockAP);
                    // fall through
                case(ANDROID_PREPARING):
                case(ANDROID_READY):
                    if (ap->mSfPlayer != 0) {
                        ap->mSfPlayer->play();
                    }
                    break;
                default:
                    break;
            }
            } break;

        default:
            // checked by caller, should not happen
            break;
        }
        break;
#endif
    default:
        break;
    }
}


//-----------------------------------------------------------------------------
void android_audioPlayer_useEventMask(CAudioPlayer *ap) {
    IPlay *pPlayItf = &ap->mPlay;
    SLuint32 eventFlags = pPlayItf->mEventFlags;
    /*switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:*/

    if (NULL == ap->mAudioTrack) {
        return;
    }

    if (eventFlags & SL_PLAYEVENT_HEADATMARKER) {
        ap->mAudioTrack->setMarkerPosition((uint32_t)((((int64_t)pPlayItf->mMarkerPosition
                * sles_to_android_sampleRate(ap->mSampleRateMilliHz)))/1000));
    } else {
        // clear marker
        ap->mAudioTrack->setMarkerPosition(0);
    }

    if (eventFlags & SL_PLAYEVENT_HEADATNEWPOS) {
         ap->mAudioTrack->setPositionUpdatePeriod(
                (uint32_t)((((int64_t)pPlayItf->mPositionUpdatePeriod
                * sles_to_android_sampleRate(ap->mSampleRateMilliHz)))/1000));
    } else {
        // clear periodic update
        ap->mAudioTrack->setPositionUpdatePeriod(0);
    }

    if (eventFlags & SL_PLAYEVENT_HEADATEND) {
        // nothing to do for SL_PLAYEVENT_HEADATEND, callback event will be checked against mask
    }

    if (eventFlags & SL_PLAYEVENT_HEADMOVING) {
        // FIXME support SL_PLAYEVENT_HEADMOVING
        SL_LOGD("[ FIXME: IPlay_SetCallbackEventsMask(SL_PLAYEVENT_HEADMOVING) on an "
            "SL_OBJECTID_AUDIOPLAYER to be implemented ]");
    }
    if (eventFlags & SL_PLAYEVENT_HEADSTALLED) {
        // nothing to do for SL_PLAYEVENT_HEADSTALLED, callback event will be checked against mask
    }

}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_getDuration(IPlay *pPlayItf, SLmillisecond *pDurMsec) {
    CAudioPlayer *ap = (CAudioPlayer *)pPlayItf->mThis;
    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
        *pDurMsec = SL_TIME_UNKNOWN;
        // FIXME if the data source is not a buffer queue, and the audio data is saved in
        //       shared memory with the mixer process, the duration is the size of the buffer
        SL_LOGD("FIXME: android_audioPlayer_getDuration() verify if duration can be retrieved");
        break;
#ifndef USE_BACKPORT
    case MEDIAPLAYER: {
        int64_t durationUsec = SL_TIME_UNKNOWN;
        if (ap->mSfPlayer != 0) {
            durationUsec = ap->mSfPlayer->getDurationUsec();
            *pDurMsec = durationUsec == -1 ? SL_TIME_UNKNOWN : durationUsec / 1000;
        }
        } break;
#endif
    default:
        break;
    }
    return SL_RESULT_SUCCESS;
}


//-----------------------------------------------------------------------------
void android_audioPlayer_getPosition(IPlay *pPlayItf, SLmillisecond *pPosMsec) {
    CAudioPlayer *ap = (CAudioPlayer *)pPlayItf->mThis;
    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
        if ((ap->mSampleRateMilliHz == 0) || (NULL == ap->mAudioTrack)) {
            *pPosMsec = 0;
        } else {
            uint32_t positionInFrames;
            ap->mAudioTrack->getPosition(&positionInFrames);
            *pPosMsec = ((int64_t)positionInFrames * 1000) /
                    sles_to_android_sampleRate(ap->mSampleRateMilliHz);
        }
        break;
    case MEDIAPLAYER:
        if (ap->mSfPlayer != 0) {
            *pPosMsec = ap->mSfPlayer->getPositionMsec();
        } else {
            *pPosMsec = 0;
        }
        break;
    default:
        break;
    }
}


//-----------------------------------------------------------------------------
void android_audioPlayer_seek(CAudioPlayer *ap, SLmillisecond posMsec) {

    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
        break;
#ifndef USE_BACKPORT
    case MEDIAPLAYER:
        if (ap->mSfPlayer != 0) {
            ap->mSfPlayer->seek(posMsec);
        }
        break;
#endif
    default:
        break;
    }
}


//-----------------------------------------------------------------------------
void android_audioPlayer_loop(CAudioPlayer *ap, SLboolean loopEnable) {

    if ((MEDIAPLAYER == ap->mAndroidObjType) && (ap->mSfPlayer != 0)) {
        ap->mSfPlayer->loop((bool)loopEnable);
    }
}


//-----------------------------------------------------------------------------
/*
 * Mutes or unmutes the Android media framework object associated with the CAudioPlayer that carries
 * the IVolume interface.
 * Pre-condition:
 *   if ap->mMute is SL_BOOLEAN_FALSE, a call to this function was preceded by a call
 *   to android_audioPlayer_volumeUpdate()
 */
static void android_audioPlayer_setMute(CAudioPlayer* ap) {
    android::AudioTrack *t = NULL;
    switch(ap->mAndroidObjType) {
    case AUDIOTRACK_PULL:
    case MEDIAPLAYER:
        t = ap->mAudioTrack;
        break;
    default:
        break;
    }
    // when unmuting: volume levels have already been updated in IVolume_SetMute
    if (NULL != t) {
        t->mute(ap->mMute);
    }
}


//-----------------------------------------------------------------------------
SLresult android_audioPlayer_volumeUpdate(CAudioPlayer* ap) {
    android_audioPlayer_updateStereoVolume(ap);
    android_audioPlayer_setMute(ap);
    return SL_RESULT_SUCCESS;
}


//-----------------------------------------------------------------------------
void android_audioPlayer_bufferQueue_onRefilled(CAudioPlayer *ap) {
    // the AudioTrack associated with the AudioPlayer receiving audio from a PCM buffer
    // queue was stopped when the queue become empty, we restart as soon as a new buffer
    // has been enqueued since we're in playing state
    if (NULL != ap->mAudioTrack) {
        ap->mAudioTrack->start();
    }

    // when the queue became empty, an underflow on the prefetch status itf was sent. Now the queue
    // has received new data, signal it has sufficient data
    if (IsInterfaceInitialized(&(ap->mObject), MPH_PREFETCHSTATUS)) {
        audioPlayer_dispatch_prefetchStatus_lockPrefetch(ap, SL_PREFETCHSTATUS_SUFFICIENTDATA,
            true);
    }
}


//-----------------------------------------------------------------------------
/*
 * BufferQueue::Clear
 */
SLresult android_audioPlayer_bufferQueue_onClear(CAudioPlayer *ap) {
    SLresult result = SL_RESULT_SUCCESS;

    switch (ap->mAndroidObjType) {
    //-----------------------------------
    // AudioTrack
    case AUDIOTRACK_PULL:
        if (NULL != ap->mAudioTrack) {
            ap->mAudioTrack->flush();
        }
        break;
    default:
        result = SL_RESULT_INTERNAL_ERROR;
        break;
    }

    return result;
}

