1 /*
2 * Copyright (C) 2012 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//#define LOG_NDEBUG 0
18#define LOG_TAG "TimedTextPlayer"
19#include <utils/Log.h>
20
21#include <inttypes.h>
22#include <limits.h>
23#include <media/stagefright/foundation/ADebug.h>
24#include <media/stagefright/foundation/AMessage.h>
25#include <media/stagefright/timedtext/TimedTextDriver.h>
26#include <media/stagefright/MediaErrors.h>
27#include <media/MediaPlayerInterface.h>
28
29#include "TimedTextPlayer.h"
30
31#include "TimedTextSource.h"
32
33namespace android {
34
35// Event should be fired a bit earlier considering the processing time till
36// application actually gets the notification message.
37static const int64_t kAdjustmentProcessingTimeUs = 100000ll;
38static const int64_t kMaxDelayUs = 5000000ll;
39static const int64_t kWaitTimeUsToRetryRead = 100000ll;
40static const int64_t kInvalidTimeUs = INT_MIN;
41
42TimedTextPlayer::TimedTextPlayer(const wp<MediaPlayerBase> &listener)
43    : mListener(listener),
44      mSource(NULL),
45      mPendingSeekTimeUs(kInvalidTimeUs),
46      mPaused(false),
47      mSendSubtitleGeneration(0) {
48}
49
50TimedTextPlayer::~TimedTextPlayer() {
51    if (mSource != NULL) {
52        mSource->stop();
53        mSource.clear();
54        mSource = NULL;
55    }
56}
57
58void TimedTextPlayer::start() {
59    (new AMessage(kWhatStart, id()))->post();
60}
61
62void TimedTextPlayer::pause() {
63    (new AMessage(kWhatPause, id()))->post();
64}
65
66void TimedTextPlayer::resume() {
67    (new AMessage(kWhatResume, id()))->post();
68}
69
70void TimedTextPlayer::seekToAsync(int64_t timeUs) {
71    sp<AMessage> msg = new AMessage(kWhatSeek, id());
72    msg->setInt64("seekTimeUs", timeUs);
73    msg->post();
74}
75
76void TimedTextPlayer::setDataSource(sp<TimedTextSource> source) {
77    sp<AMessage> msg = new AMessage(kWhatSetSource, id());
78    msg->setObject("source", source);
79    msg->post();
80}
81
82void TimedTextPlayer::onMessageReceived(const sp<AMessage> &msg) {
83    switch (msg->what()) {
84        case kWhatPause: {
85            mPaused = true;
86            break;
87        }
88        case kWhatResume: {
89            mPaused = false;
90            if (mPendingSeekTimeUs != kInvalidTimeUs) {
91                seekToAsync(mPendingSeekTimeUs);
92                mPendingSeekTimeUs = kInvalidTimeUs;
93            } else {
94                doRead();
95            }
96            break;
97        }
98        case kWhatStart: {
99            sp<MediaPlayerBase> listener = mListener.promote();
100            if (listener == NULL) {
101                ALOGE("Listener is NULL when kWhatStart is received.");
102                break;
103            }
104            mPaused = false;
105            mPendingSeekTimeUs = kInvalidTimeUs;
106            int32_t positionMs = 0;
107            listener->getCurrentPosition(&positionMs);
108            int64_t seekTimeUs = positionMs * 1000ll;
109
110            notifyListener();
111            mSendSubtitleGeneration++;
112            doSeekAndRead(seekTimeUs);
113            break;
114        }
115        case kWhatRetryRead: {
116            int32_t generation = -1;
117            CHECK(msg->findInt32("generation", &generation));
118            if (generation != mSendSubtitleGeneration) {
119                // Drop obsolete msg.
120                break;
121            }
122            int64_t seekTimeUs;
123            int seekMode;
124            if (msg->findInt64("seekTimeUs", &seekTimeUs) &&
125                msg->findInt32("seekMode", &seekMode)) {
126                MediaSource::ReadOptions options;
127                options.setSeekTo(
128                    seekTimeUs,
129                    static_cast<MediaSource::ReadOptions::SeekMode>(seekMode));
130                doRead(&options);
131            } else {
132                doRead();
133            }
134            break;
135        }
136        case kWhatSeek: {
137            int64_t seekTimeUs = kInvalidTimeUs;
138            // Clear a displayed timed text before seeking.
139            notifyListener();
140            msg->findInt64("seekTimeUs", &seekTimeUs);
141            if (seekTimeUs == kInvalidTimeUs) {
142                sp<MediaPlayerBase> listener = mListener.promote();
143                if (listener != NULL) {
144                    int32_t positionMs = 0;
145                    listener->getCurrentPosition(&positionMs);
146                    seekTimeUs = positionMs * 1000ll;
147                }
148            }
149            if (mPaused) {
150                mPendingSeekTimeUs = seekTimeUs;
151                break;
152            }
153            mSendSubtitleGeneration++;
154            doSeekAndRead(seekTimeUs);
155            break;
156        }
157        case kWhatSendSubtitle: {
158            int32_t generation;
159            CHECK(msg->findInt32("generation", &generation));
160            if (generation != mSendSubtitleGeneration) {
161                // Drop obsolete msg.
162                break;
163            }
164            // If current time doesn't reach to the fire time,
165            // re-post the message with the adjusted delay time.
166            int64_t fireTimeUs = kInvalidTimeUs;
167            if (msg->findInt64("fireTimeUs", &fireTimeUs)) {
168                // TODO: check if fireTimeUs is not kInvalidTimeUs.
169                int64_t delayUs = delayUsFromCurrentTime(fireTimeUs);
170                if (delayUs > 0) {
171                    msg->post(delayUs);
172                    break;
173                }
174            }
175            sp<RefBase> obj;
176            if (msg->findObject("subtitle", &obj)) {
177                sp<ParcelEvent> parcelEvent;
178                parcelEvent = static_cast<ParcelEvent*>(obj.get());
179                notifyListener(&(parcelEvent->parcel));
180                doRead();
181            } else {
182                notifyListener();
183            }
184            break;
185        }
186        case kWhatSetSource: {
187            mSendSubtitleGeneration++;
188            sp<RefBase> obj;
189            msg->findObject("source", &obj);
190            if (mSource != NULL) {
191                mSource->stop();
192                mSource.clear();
193                mSource = NULL;
194            }
195            // null source means deselect track.
196            if (obj == NULL) {
197                mPendingSeekTimeUs = kInvalidTimeUs;
198                mPaused = false;
199                notifyListener();
200                break;
201            }
202            mSource = static_cast<TimedTextSource*>(obj.get());
203            status_t err = mSource->start();
204            if (err != OK) {
205                notifyError(err);
206                break;
207            }
208            Parcel parcel;
209            err = mSource->extractGlobalDescriptions(&parcel);
210            if (err != OK) {
211                notifyError(err);
212                break;
213            }
214            notifyListener(&parcel);
215            break;
216        }
217    }
218}
219
220void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) {
221    MediaSource::ReadOptions options;
222    options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
223    doRead(&options);
224}
225
226void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) {
227    int64_t startTimeUs = 0;
228    int64_t endTimeUs = 0;
229    sp<ParcelEvent> parcelEvent = new ParcelEvent();
230    CHECK(mSource != NULL);
231    status_t err = mSource->read(&startTimeUs, &endTimeUs,
232                                 &(parcelEvent->parcel), options);
233    if (err == WOULD_BLOCK) {
234        sp<AMessage> msg = new AMessage(kWhatRetryRead, id());
235        if (options != NULL) {
236            int64_t seekTimeUs = kInvalidTimeUs;
237            MediaSource::ReadOptions::SeekMode seekMode =
238                MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC;
239            CHECK(options->getSeekTo(&seekTimeUs, &seekMode));
240            msg->setInt64("seekTimeUs", seekTimeUs);
241            msg->setInt32("seekMode", seekMode);
242        }
243        msg->setInt32("generation", mSendSubtitleGeneration);
244        msg->post(kWaitTimeUsToRetryRead);
245        return;
246    } else if (err != OK) {
247        notifyError(err);
248        return;
249    }
250
251    postTextEvent(parcelEvent, startTimeUs);
252    if (endTimeUs > 0) {
253        CHECK_GE(endTimeUs, startTimeUs);
254        // send an empty timed text to clear the subtitle when it reaches to the
255        // end time.
256        postTextEvent(NULL, endTimeUs);
257    }
258}
259
260void TimedTextPlayer::postTextEvent(const sp<ParcelEvent>& parcel, int64_t timeUs) {
261    int64_t delayUs = delayUsFromCurrentTime(timeUs);
262    sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id());
263    msg->setInt32("generation", mSendSubtitleGeneration);
264    if (parcel != NULL) {
265        msg->setObject("subtitle", parcel);
266    }
267    msg->setInt64("fireTimeUs", timeUs);
268    msg->post(delayUs);
269}
270
271int64_t TimedTextPlayer::delayUsFromCurrentTime(int64_t fireTimeUs) {
272    sp<MediaPlayerBase> listener = mListener.promote();
273    if (listener == NULL) {
274        // TODO: it may be better to return kInvalidTimeUs
275        ALOGE("%s: Listener is NULL. (fireTimeUs = %" PRId64" )",
276              __FUNCTION__, fireTimeUs);
277        return 0;
278    }
279    int32_t positionMs = 0;
280    listener->getCurrentPosition(&positionMs);
281    int64_t positionUs = positionMs * 1000ll;
282
283    if (fireTimeUs <= positionUs + kAdjustmentProcessingTimeUs) {
284        return 0;
285    } else {
286        int64_t delayUs = fireTimeUs - positionUs - kAdjustmentProcessingTimeUs;
287        if (delayUs > kMaxDelayUs) {
288            return kMaxDelayUs;
289        }
290        return delayUs;
291    }
292}
293
294void TimedTextPlayer::notifyError(int error) {
295    sp<MediaPlayerBase> listener = mListener.promote();
296    if (listener == NULL) {
297        ALOGE("%s(error=%d): Listener is NULL.", __FUNCTION__, error);
298        return;
299    }
300    listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error);
301}
302
303void TimedTextPlayer::notifyListener(const Parcel *parcel) {
304    sp<MediaPlayerBase> listener = mListener.promote();
305    if (listener == NULL) {
306        ALOGE("%s: Listener is NULL.", __FUNCTION__);
307        return;
308    }
309    if (parcel != NULL && (parcel->dataSize() > 0)) {
310        listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel);
311    } else {  // send an empty timed text to clear the screen
312        listener->sendEvent(MEDIA_TIMED_TEXT);
313    }
314}
315
316}  // namespace android
317