1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/* Play implementation */
18
19#include "sles_allinclusive.h"
20
21
22static SLresult IPlay_SetPlayState(SLPlayItf self, SLuint32 state)
23{
24    SL_ENTER_INTERFACE
25
26    switch (state) {
27    case SL_PLAYSTATE_STOPPED:
28    case SL_PLAYSTATE_PAUSED:
29    case SL_PLAYSTATE_PLAYING:
30        {
31        IPlay *this = (IPlay *) self;
32        unsigned attr = ATTR_NONE;
33        result = SL_RESULT_SUCCESS;
34        CAudioPlayer *audioPlayer = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ?
35            (CAudioPlayer *) this->mThis : NULL;
36        interface_lock_exclusive(this);
37#ifdef USE_OUTPUTMIXEXT
38        for (;; interface_cond_wait(this)) {
39
40            // We are comparing the old state (left) vs. new state (right).
41            // Note that the old state is 3 bits wide, but new state is only 2 bits wide.
42            // That is why the old state is on the left and new state is on the right.
43            switch ((this->mState << 2) | state) {
44
45            case (SL_PLAYSTATE_STOPPED  << 2) | SL_PLAYSTATE_STOPPED:
46            case (SL_PLAYSTATE_PAUSED   << 2) | SL_PLAYSTATE_PAUSED:
47            case (SL_PLAYSTATE_PLAYING  << 2) | SL_PLAYSTATE_PLAYING:
48               // no-op
49                break;
50
51            case (SL_PLAYSTATE_STOPPED  << 2) | SL_PLAYSTATE_PLAYING:
52            case (SL_PLAYSTATE_PAUSED   << 2) | SL_PLAYSTATE_PLAYING:
53                attr = ATTR_TRANSPORT;
54                // set enqueue attribute if queue is non-empty and state becomes PLAYING
55                if ((NULL != audioPlayer) && (audioPlayer->mBufferQueue.mFront !=
56                    audioPlayer->mBufferQueue.mRear)) {
57                    attr |= ATTR_ENQUEUE;
58                }
59                // fall through
60
61            case (SL_PLAYSTATE_STOPPED  << 2) | SL_PLAYSTATE_PAUSED:
62            case (SL_PLAYSTATE_PLAYING  << 2) | SL_PLAYSTATE_PAUSED:
63                // easy
64                this->mState = state;
65                break;
66
67            case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_STOPPED:
68                // either spurious wakeup, or someone else requested same transition
69                continue;
70
71            case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_PAUSED:
72            case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_PLAYING:
73                // wait for other guy to finish his transition, then retry ours
74                continue;
75
76            case (SL_PLAYSTATE_PAUSED   << 2) | SL_PLAYSTATE_STOPPED:
77            case (SL_PLAYSTATE_PLAYING  << 2) | SL_PLAYSTATE_STOPPED:
78                // tell mixer to stop, then wait for mixer to acknowledge the request to stop
79                this->mState = SL_PLAYSTATE_STOPPING;
80                continue;
81
82            default:
83                // unexpected state
84                assert(SL_BOOLEAN_FALSE);
85                result = SL_RESULT_INTERNAL_ERROR;
86                break;
87
88            }
89
90            break;
91        }
92#else
93        // Here life looks easy for an Android, but there are other troubles in play land
94        this->mState = state;
95        attr = ATTR_TRANSPORT;
96#endif
97        interface_unlock_exclusive_attributes(this, attr);
98        }
99        break;
100    default:
101        result = SL_RESULT_PARAMETER_INVALID;
102        break;
103    }
104
105    SL_LEAVE_INTERFACE
106}
107
108
109static SLresult IPlay_GetPlayState(SLPlayItf self, SLuint32 *pState)
110{
111    SL_ENTER_INTERFACE
112
113    if (NULL == pState) {
114        result = SL_RESULT_PARAMETER_INVALID;
115    } else {
116        IPlay *this = (IPlay *) self;
117        interface_lock_peek(this);
118        SLuint32 state = this->mState;
119        interface_unlock_peek(this);
120        result = SL_RESULT_SUCCESS;
121#ifdef USE_OUTPUTMIXEXT
122        switch (state) {
123        case SL_PLAYSTATE_STOPPED:  // as is
124        case SL_PLAYSTATE_PAUSED:
125        case SL_PLAYSTATE_PLAYING:
126            break;
127        case SL_PLAYSTATE_STOPPING: // these states require re-mapping
128            state = SL_PLAYSTATE_STOPPED;
129            break;
130        default:                    // impossible
131            assert(SL_BOOLEAN_FALSE);
132            state = SL_PLAYSTATE_STOPPED;
133            result = SL_RESULT_INTERNAL_ERROR;
134            break;
135        }
136#endif
137        *pState = state;
138    }
139
140    SL_LEAVE_INTERFACE
141}
142
143
144static SLresult IPlay_GetDuration(SLPlayItf self, SLmillisecond *pMsec)
145{
146    SL_ENTER_INTERFACE
147
148    if (NULL == pMsec) {
149        result = SL_RESULT_PARAMETER_INVALID;
150    } else {
151        result = SL_RESULT_SUCCESS;
152        IPlay *this = (IPlay *) self;
153        // even though this is a getter, it can modify state due to caching
154        interface_lock_exclusive(this);
155        SLmillisecond duration = this->mDuration;
156#ifdef ANDROID
157        if ((SL_TIME_UNKNOWN == duration) &&
158            (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this))) {
159            SLmillisecond temp;
160            result = android_audioPlayer_getDuration(this, &temp);
161            if (SL_RESULT_SUCCESS == result) {
162                duration = temp;
163                this->mDuration = duration;
164            }
165        }
166#else
167        // will be set by containing AudioPlayer or MidiPlayer object at Realize, if known,
168        // otherwise the duration will be updated each time a new maximum position is detected
169#endif
170        interface_unlock_exclusive(this);
171        *pMsec = duration;
172    }
173
174    SL_LEAVE_INTERFACE
175}
176
177
178static SLresult IPlay_GetPosition(SLPlayItf self, SLmillisecond *pMsec)
179{
180    SL_ENTER_INTERFACE
181
182    if (NULL == pMsec) {
183        result = SL_RESULT_PARAMETER_INVALID;
184    } else {
185        IPlay *this = (IPlay *) self;
186        SLmillisecond position;
187        interface_lock_shared(this);
188#ifdef ANDROID
189        // Android does not use the mPosition field for audio players
190        if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) {
191            android_audioPlayer_getPosition(this, &position);
192            // note that we do not cache the position
193        } else {
194            position = this->mPosition;
195        }
196#else
197        // on other platforms we depend on periodic updates to the current position
198        position = this->mPosition;
199        // if a seek is pending, then lie about current position so the seek appears synchronous
200        if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) {
201            CAudioPlayer *audioPlayer = (CAudioPlayer *) this->mThis;
202            SLmillisecond pos = audioPlayer->mSeek.mPos;
203            if (SL_TIME_UNKNOWN != pos) {
204                position = pos;
205            }
206        }
207#endif
208        interface_unlock_shared(this);
209        *pMsec = position;
210        result = SL_RESULT_SUCCESS;
211    }
212
213    SL_LEAVE_INTERFACE
214}
215
216
217static SLresult IPlay_RegisterCallback(SLPlayItf self, slPlayCallback callback, void *pContext)
218{
219    SL_ENTER_INTERFACE
220
221    IPlay *this = (IPlay *) self;
222    interface_lock_exclusive(this);
223    this->mCallback = callback;
224    this->mContext = pContext;
225    // omits _attributes b/c noone cares deeply enough about these fields to need quick notification
226    interface_unlock_exclusive(this);
227    result = SL_RESULT_SUCCESS;
228
229    SL_LEAVE_INTERFACE
230}
231
232
233static SLresult IPlay_SetCallbackEventsMask(SLPlayItf self, SLuint32 eventFlags)
234{
235    SL_ENTER_INTERFACE
236
237    if (eventFlags & ~(SL_PLAYEVENT_HEADATEND | SL_PLAYEVENT_HEADATMARKER |
238            SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADMOVING | SL_PLAYEVENT_HEADSTALLED)) {
239        result = SL_RESULT_PARAMETER_INVALID;
240    } else {
241        IPlay *this = (IPlay *) self;
242        interface_lock_exclusive(this);
243        if (this->mEventFlags != eventFlags) {
244#ifdef USE_OUTPUTMIXEXT
245            // enabling the "head at new position" play event will postpone the next update event
246            if (!(this->mEventFlags & SL_PLAYEVENT_HEADATNEWPOS) &&
247                    (eventFlags & SL_PLAYEVENT_HEADATNEWPOS)) {
248                this->mFramesSincePositionUpdate = 0;
249            }
250#endif
251            this->mEventFlags = eventFlags;
252            interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT);
253        } else
254            interface_unlock_exclusive(this);
255        result = SL_RESULT_SUCCESS;
256    }
257
258    SL_LEAVE_INTERFACE
259}
260
261
262static SLresult IPlay_GetCallbackEventsMask(SLPlayItf self, SLuint32 *pEventFlags)
263{
264    SL_ENTER_INTERFACE
265
266    if (NULL == pEventFlags) {
267        result = SL_RESULT_PARAMETER_INVALID;
268    } else {
269        IPlay *this = (IPlay *) self;
270        interface_lock_peek(this);
271        SLuint32 eventFlags = this->mEventFlags;
272        interface_unlock_peek(this);
273        *pEventFlags = eventFlags;
274        result = SL_RESULT_SUCCESS;
275    }
276
277    SL_LEAVE_INTERFACE
278}
279
280
281static SLresult IPlay_SetMarkerPosition(SLPlayItf self, SLmillisecond mSec)
282{
283    SL_ENTER_INTERFACE
284
285    IPlay *this = (IPlay *) self;
286    interface_lock_exclusive(this);
287    if (this->mMarkerPosition != mSec) {
288        this->mMarkerPosition = mSec;
289        interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT);
290    } else
291        interface_unlock_exclusive(this);
292    result = SL_RESULT_SUCCESS;
293
294    SL_LEAVE_INTERFACE
295}
296
297
298static SLresult IPlay_ClearMarkerPosition(SLPlayItf self)
299{
300    SL_ENTER_INTERFACE
301
302    IPlay *this = (IPlay *) self;
303    interface_lock_exclusive(this);
304#ifdef ANDROID
305    if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) {
306        // clearing the marker position is equivalent to setting the marker at 0
307        this->mMarkerPosition = 0;
308    }
309#endif
310    interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT);
311    result = SL_RESULT_SUCCESS;
312
313    SL_LEAVE_INTERFACE
314}
315
316
317static SLresult IPlay_GetMarkerPosition(SLPlayItf self, SLmillisecond *pMsec)
318{
319    SL_ENTER_INTERFACE
320
321    if (NULL == pMsec) {
322        result = SL_RESULT_PARAMETER_INVALID;
323    } else {
324        IPlay *this = (IPlay *) self;
325        interface_lock_peek(this);
326        SLmillisecond markerPosition = this->mMarkerPosition;
327        interface_unlock_peek(this);
328        *pMsec = markerPosition;
329        result = SL_RESULT_SUCCESS;
330    }
331
332    SL_LEAVE_INTERFACE
333}
334
335
336static SLresult IPlay_SetPositionUpdatePeriod(SLPlayItf self, SLmillisecond mSec)
337{
338    SL_ENTER_INTERFACE
339
340    if (0 == mSec) {
341        result = SL_RESULT_PARAMETER_INVALID;
342    } else {
343        IPlay *this = (IPlay *) self;
344        interface_lock_exclusive(this);
345        if (this->mPositionUpdatePeriod != mSec) {
346            this->mPositionUpdatePeriod = mSec;
347#ifdef ANDROID
348            if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) {
349                // result = android_audioPlayer_useEventMask(this, this->mEventFlags);
350            }
351#endif
352#ifdef USE_OUTPUTMIXEXT
353            if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) {
354                CAudioPlayer *audioPlayer = (CAudioPlayer *) this->mThis;
355                SLuint32 frameUpdatePeriod = ((long long) mSec *
356                    (long long) audioPlayer->mSampleRateMilliHz) / 1000000LL;
357                if (0 == frameUpdatePeriod)
358                    frameUpdatePeriod = ~0;
359                this->mFrameUpdatePeriod = frameUpdatePeriod;
360                // setting a new update period postpones the next callback
361                this->mFramesSincePositionUpdate = 0;
362            }
363#endif
364            interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT);
365        } else
366            interface_unlock_exclusive(this);
367        result = SL_RESULT_SUCCESS;
368    }
369
370    SL_LEAVE_INTERFACE
371}
372
373
374static SLresult IPlay_GetPositionUpdatePeriod(SLPlayItf self, SLmillisecond *pMsec)
375{
376    SL_ENTER_INTERFACE
377
378    if (NULL == pMsec) {
379        result = SL_RESULT_PARAMETER_INVALID;
380    } else {
381        IPlay *this = (IPlay *) self;
382        interface_lock_peek(this);
383        SLmillisecond positionUpdatePeriod = this->mPositionUpdatePeriod;
384        interface_unlock_peek(this);
385        *pMsec = positionUpdatePeriod;
386        result = SL_RESULT_SUCCESS;
387    }
388
389    SL_LEAVE_INTERFACE
390}
391
392
393static const struct SLPlayItf_ IPlay_Itf = {
394    IPlay_SetPlayState,
395    IPlay_GetPlayState,
396    IPlay_GetDuration,
397    IPlay_GetPosition,
398    IPlay_RegisterCallback,
399    IPlay_SetCallbackEventsMask,
400    IPlay_GetCallbackEventsMask,
401    IPlay_SetMarkerPosition,
402    IPlay_ClearMarkerPosition,
403    IPlay_GetMarkerPosition,
404    IPlay_SetPositionUpdatePeriod,
405    IPlay_GetPositionUpdatePeriod
406};
407
408void IPlay_init(void *self)
409{
410    IPlay *this = (IPlay *) self;
411    this->mItf = &IPlay_Itf;
412    this->mState = SL_PLAYSTATE_STOPPED;
413    this->mDuration = SL_TIME_UNKNOWN;  // will be set by containing player object
414    this->mPosition = (SLmillisecond) 0;
415    this->mCallback = NULL;
416    this->mContext = NULL;
417    this->mEventFlags = 0;
418    this->mMarkerPosition = 0;
419    this->mPositionUpdatePeriod = 1000;
420#ifdef USE_OUTPUTMIXEXT
421    this->mFrameUpdatePeriod = 0;   // because we don't know the sample rate yet
422    this->mLastSeekPosition = 0;
423    this->mFramesSinceLastSeek = 0;
424    this->mFramesSincePositionUpdate = 0;
425#endif
426}
427