/*
* 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.
*/
/* Play implementation */
#include "sles_allinclusive.h"
static SLresult IPlay_SetPlayState(SLPlayItf self, SLuint32 state)
{
SL_ENTER_INTERFACE
switch (state) {
case SL_PLAYSTATE_STOPPED:
case SL_PLAYSTATE_PAUSED:
case SL_PLAYSTATE_PLAYING:
{
IPlay *thiz = (IPlay *) self;
unsigned attr = ATTR_NONE;
result = SL_RESULT_SUCCESS;
#ifdef USE_OUTPUTMIXEXT
CAudioPlayer *audioPlayer = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(thiz)) ?
(CAudioPlayer *) thiz->mThis : NULL;
#endif
interface_lock_exclusive(thiz);
SLuint32 oldState = thiz->mState;
if (state != oldState) {
#ifdef USE_OUTPUTMIXEXT
for (;; interface_cond_wait(thiz)) {
// We are comparing the old state (left) vs. new state (right).
// Note that the old state is 3 bits wide, but new state is only 2 bits wide.
// That is why the old state is on the left and new state is on the right.
switch ((oldState << 2) | state) {
case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_STOPPED:
case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_PAUSED:
case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_PLAYING:
// no-op, and unreachable due to earlier "if (state != oldState)"
break;
case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_PLAYING:
case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_PLAYING:
attr = ATTR_PLAY_STATE;
// set enqueue attribute if queue is non-empty and state becomes PLAYING
if ((NULL != audioPlayer) && (audioPlayer->mBufferQueue.mFront !=
audioPlayer->mBufferQueue.mRear)) {
// note that USE_OUTPUTMIXEXT does not support ATTR_ABQ_ENQUEUE
attr |= ATTR_BQ_ENQUEUE;
}
// fall through
case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_PAUSED:
case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_PAUSED:
// easy
thiz->mState = state;
break;
case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_STOPPED:
// either spurious wakeup, or someone else requested same transition
continue;
case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_PAUSED:
case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_PLAYING:
// wait for other guy to finish his transition, then retry ours
continue;
case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_STOPPED:
case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_STOPPED:
// tell mixer to stop, then wait for mixer to acknowledge the request to stop
thiz->mState = SL_PLAYSTATE_STOPPING;
continue;
default:
// unexpected state
assert(SL_BOOLEAN_FALSE);
result = SL_RESULT_INTERNAL_ERROR;
break;
}
break;
}
#else
// Here life looks easy for an Android, but there are other troubles in play land
thiz->mState = state;
attr = ATTR_PLAY_STATE;
// no need to set ATTR_BQ_ENQUEUE or ATTR_ABQ_ENQUEUE
#endif
}
// SL_LOGD("set play state %d", state);
interface_unlock_exclusive_attributes(thiz, attr);
}
break;
default:
result = SL_RESULT_PARAMETER_INVALID;
break;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_GetPlayState(SLPlayItf self, SLuint32 *pState)
{
SL_ENTER_INTERFACE
if (NULL == pState) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
interface_lock_shared(thiz);
SLuint32 state = thiz->mState;
interface_unlock_shared(thiz);
result = SL_RESULT_SUCCESS;
#ifdef USE_OUTPUTMIXEXT
switch (state) {
case SL_PLAYSTATE_STOPPED: // as is
case SL_PLAYSTATE_PAUSED:
case SL_PLAYSTATE_PLAYING:
break;
case SL_PLAYSTATE_STOPPING: // these states require re-mapping
state = SL_PLAYSTATE_STOPPED;
break;
default: // impossible
assert(SL_BOOLEAN_FALSE);
state = SL_PLAYSTATE_STOPPED;
result = SL_RESULT_INTERNAL_ERROR;
break;
}
#endif
*pState = state;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_GetDuration(SLPlayItf self, SLmillisecond *pMsec)
{
SL_ENTER_INTERFACE
if (NULL == pMsec) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
result = SL_RESULT_SUCCESS;
IPlay *thiz = (IPlay *) self;
// even though this is a getter, it can modify state due to caching
interface_lock_exclusive(thiz);
SLmillisecond duration = thiz->mDuration;
#ifdef ANDROID
if (SL_TIME_UNKNOWN == duration) {
SLmillisecond temp;
switch (InterfaceToObjectID(thiz)) {
case SL_OBJECTID_AUDIOPLAYER:
result = android_audioPlayer_getDuration(thiz, &temp);
break;
case XA_OBJECTID_MEDIAPLAYER:
result = android_Player_getDuration(thiz, &temp);
break;
default:
result = SL_RESULT_FEATURE_UNSUPPORTED;
break;
}
if (SL_RESULT_SUCCESS == result) {
duration = temp;
thiz->mDuration = duration;
}
}
#else
// will be set by containing AudioPlayer or MidiPlayer object at Realize, if known,
// otherwise the duration will be updated each time a new maximum position is detected
#endif
interface_unlock_exclusive(thiz);
*pMsec = duration;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_GetPosition(SLPlayItf self, SLmillisecond *pMsec)
{
SL_ENTER_INTERFACE
if (NULL == pMsec) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
SLmillisecond position;
interface_lock_shared(thiz);
#ifdef ANDROID
// Android does not use the mPosition field for audio and media players
// and doesn't cache the position
switch (IObjectToObjectID((thiz)->mThis)) {
case SL_OBJECTID_AUDIOPLAYER:
android_audioPlayer_getPosition(thiz, &position);
break;
case XA_OBJECTID_MEDIAPLAYER:
android_Player_getPosition(thiz, &position);
break;
default:
// we shouldn'be here
assert(SL_BOOLEAN_FALSE);
}
#else
// on other platforms we depend on periodic updates to the current position
position = thiz->mPosition;
// if a seek is pending, then lie about current position so the seek appears synchronous
if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(thiz)) {
CAudioPlayer *audioPlayer = (CAudioPlayer *) thiz->mThis;
SLmillisecond pos = audioPlayer->mSeek.mPos;
if (SL_TIME_UNKNOWN != pos) {
position = pos;
}
}
#endif
interface_unlock_shared(thiz);
*pMsec = position;
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_RegisterCallback(SLPlayItf self, slPlayCallback callback, void *pContext)
{
SL_ENTER_INTERFACE
IPlay *thiz = (IPlay *) self;
interface_lock_exclusive(thiz);
thiz->mCallback = callback;
thiz->mContext = pContext;
// omits _attributes b/c noone cares deeply enough about these fields to need quick notification
interface_unlock_exclusive(thiz);
result = SL_RESULT_SUCCESS;
SL_LEAVE_INTERFACE
}
static SLresult IPlay_SetCallbackEventsMask(SLPlayItf self, SLuint32 eventFlags)
{
SL_ENTER_INTERFACE
if (eventFlags & ~(SL_PLAYEVENT_HEADATEND | SL_PLAYEVENT_HEADATMARKER |
SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADMOVING | SL_PLAYEVENT_HEADSTALLED)) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
interface_lock_exclusive(thiz);
if (thiz->mEventFlags != eventFlags) {
#ifdef USE_OUTPUTMIXEXT
// enabling the "head at new position" play event will postpone the next update event
if (!(thiz->mEventFlags & SL_PLAYEVENT_HEADATNEWPOS) &&
(eventFlags & SL_PLAYEVENT_HEADATNEWPOS)) {
thiz->mFramesSincePositionUpdate = 0;
}
#endif
thiz->mEventFlags = eventFlags;
interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT);
} else {
interface_unlock_exclusive(thiz);
}
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_GetCallbackEventsMask(SLPlayItf self, SLuint32 *pEventFlags)
{
SL_ENTER_INTERFACE
if (NULL == pEventFlags) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
interface_lock_shared(thiz);
SLuint32 eventFlags = thiz->mEventFlags;
interface_unlock_shared(thiz);
*pEventFlags = eventFlags;
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_SetMarkerPosition(SLPlayItf self, SLmillisecond mSec)
{
SL_ENTER_INTERFACE
if (SL_TIME_UNKNOWN == mSec) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
bool significant = false;
interface_lock_exclusive(thiz);
if (thiz->mMarkerPosition != mSec) {
thiz->mMarkerPosition = mSec;
if (thiz->mEventFlags & SL_PLAYEVENT_HEADATMARKER) {
significant = true;
}
}
if (significant) {
interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT);
} else {
interface_unlock_exclusive(thiz);
}
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_ClearMarkerPosition(SLPlayItf self)
{
SL_ENTER_INTERFACE
IPlay *thiz = (IPlay *) self;
bool significant = false;
interface_lock_exclusive(thiz);
// clearing the marker position is equivalent to setting the marker to SL_TIME_UNKNOWN
if (thiz->mMarkerPosition != SL_TIME_UNKNOWN) {
thiz->mMarkerPosition = SL_TIME_UNKNOWN;
if (thiz->mEventFlags & SL_PLAYEVENT_HEADATMARKER) {
significant = true;
}
}
if (significant) {
interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT);
} else {
interface_unlock_exclusive(thiz);
}
result = SL_RESULT_SUCCESS;
SL_LEAVE_INTERFACE
}
static SLresult IPlay_GetMarkerPosition(SLPlayItf self, SLmillisecond *pMsec)
{
SL_ENTER_INTERFACE
if (NULL == pMsec) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
interface_lock_shared(thiz);
SLmillisecond markerPosition = thiz->mMarkerPosition;
interface_unlock_shared(thiz);
*pMsec = markerPosition;
if (SL_TIME_UNKNOWN == markerPosition) {
result = SL_RESULT_PRECONDITIONS_VIOLATED;
} else {
result = SL_RESULT_SUCCESS;
}
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_SetPositionUpdatePeriod(SLPlayItf self, SLmillisecond mSec)
{
SL_ENTER_INTERFACE
if (0 == mSec) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
bool significant = false;
interface_lock_exclusive(thiz);
if (thiz->mPositionUpdatePeriod != mSec) {
thiz->mPositionUpdatePeriod = mSec;
#ifdef USE_OUTPUTMIXEXT
if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(thiz)) {
CAudioPlayer *audioPlayer = (CAudioPlayer *) thiz->mThis;
SLuint32 frameUpdatePeriod = ((long long) mSec *
(long long) audioPlayer->mSampleRateMilliHz) / 1000000LL;
if (0 == frameUpdatePeriod) {
frameUpdatePeriod = ~0;
}
thiz->mFrameUpdatePeriod = frameUpdatePeriod;
// setting a new update period postpones the next callback
thiz->mFramesSincePositionUpdate = 0;
}
#endif
if (thiz->mEventFlags & SL_PLAYEVENT_HEADATNEWPOS) {
significant = true;
}
}
if (significant) {
interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT);
} else {
interface_unlock_exclusive(thiz);
}
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static SLresult IPlay_GetPositionUpdatePeriod(SLPlayItf self, SLmillisecond *pMsec)
{
SL_ENTER_INTERFACE
if (NULL == pMsec) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IPlay *thiz = (IPlay *) self;
interface_lock_shared(thiz);
SLmillisecond positionUpdatePeriod = thiz->mPositionUpdatePeriod;
interface_unlock_shared(thiz);
*pMsec = positionUpdatePeriod;
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static const struct SLPlayItf_ IPlay_Itf = {
IPlay_SetPlayState,
IPlay_GetPlayState,
IPlay_GetDuration,
IPlay_GetPosition,
IPlay_RegisterCallback,
IPlay_SetCallbackEventsMask,
IPlay_GetCallbackEventsMask,
IPlay_SetMarkerPosition,
IPlay_ClearMarkerPosition,
IPlay_GetMarkerPosition,
IPlay_SetPositionUpdatePeriod,
IPlay_GetPositionUpdatePeriod
};
void IPlay_init(void *self)
{
IPlay *thiz = (IPlay *) self;
thiz->mItf = &IPlay_Itf;
thiz->mState = SL_PLAYSTATE_STOPPED;
thiz->mDuration = SL_TIME_UNKNOWN; // will be set by containing player object
thiz->mPosition = (SLmillisecond) 0;
thiz->mCallback = NULL;
thiz->mContext = NULL;
thiz->mEventFlags = 0;
thiz->mMarkerPosition = SL_TIME_UNKNOWN;
thiz->mPositionUpdatePeriod = 1000; // per spec
#ifdef USE_OUTPUTMIXEXT
thiz->mFrameUpdatePeriod = 0; // because we don't know the sample rate yet
thiz->mLastSeekPosition = 0;
thiz->mFramesSinceLastSeek = 0;
thiz->mFramesSincePositionUpdate = 0;
#endif
}