/* * Copyright (C) 2012 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 LOG_NDEBUG 0 #define LOG_TAG "TimedTextPlayer" #include #include #include #include #include #include #include #include #include "TimedTextPlayer.h" #include "TimedTextSource.h" namespace android { // Event should be fired a bit earlier considering the processing time till // application actually gets the notification message. static const int64_t kAdjustmentProcessingTimeUs = 100000ll; static const int64_t kMaxDelayUs = 5000000ll; static const int64_t kWaitTimeUsToRetryRead = 100000ll; static const int64_t kInvalidTimeUs = INT_MIN; TimedTextPlayer::TimedTextPlayer(const wp &listener) : mListener(listener), mSource(NULL), mPendingSeekTimeUs(kInvalidTimeUs), mPaused(false), mSendSubtitleGeneration(0) { } TimedTextPlayer::~TimedTextPlayer() { if (mSource != NULL) { mSource->stop(); mSource.clear(); mSource = NULL; } } void TimedTextPlayer::start() { (new AMessage(kWhatStart, id()))->post(); } void TimedTextPlayer::pause() { (new AMessage(kWhatPause, id()))->post(); } void TimedTextPlayer::resume() { (new AMessage(kWhatResume, id()))->post(); } void TimedTextPlayer::seekToAsync(int64_t timeUs) { sp msg = new AMessage(kWhatSeek, id()); msg->setInt64("seekTimeUs", timeUs); msg->post(); } void TimedTextPlayer::setDataSource(sp source) { sp msg = new AMessage(kWhatSetSource, id()); msg->setObject("source", source); msg->post(); } void TimedTextPlayer::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatPause: { mPaused = true; break; } case kWhatResume: { mPaused = false; if (mPendingSeekTimeUs != kInvalidTimeUs) { seekToAsync(mPendingSeekTimeUs); mPendingSeekTimeUs = kInvalidTimeUs; } else { doRead(); } break; } case kWhatStart: { sp listener = mListener.promote(); if (listener == NULL) { ALOGE("Listener is NULL when kWhatStart is received."); break; } mPaused = false; mPendingSeekTimeUs = kInvalidTimeUs; int32_t positionMs = 0; listener->getCurrentPosition(&positionMs); int64_t seekTimeUs = positionMs * 1000ll; notifyListener(); mSendSubtitleGeneration++; doSeekAndRead(seekTimeUs); break; } case kWhatRetryRead: { int32_t generation = -1; CHECK(msg->findInt32("generation", &generation)); if (generation != mSendSubtitleGeneration) { // Drop obsolete msg. break; } int64_t seekTimeUs; int seekMode; if (msg->findInt64("seekTimeUs", &seekTimeUs) && msg->findInt32("seekMode", &seekMode)) { MediaSource::ReadOptions options; options.setSeekTo( seekTimeUs, static_cast(seekMode)); doRead(&options); } else { doRead(); } break; } case kWhatSeek: { int64_t seekTimeUs = kInvalidTimeUs; // Clear a displayed timed text before seeking. notifyListener(); msg->findInt64("seekTimeUs", &seekTimeUs); if (seekTimeUs == kInvalidTimeUs) { sp listener = mListener.promote(); if (listener != NULL) { int32_t positionMs = 0; listener->getCurrentPosition(&positionMs); seekTimeUs = positionMs * 1000ll; } } if (mPaused) { mPendingSeekTimeUs = seekTimeUs; break; } mSendSubtitleGeneration++; doSeekAndRead(seekTimeUs); break; } case kWhatSendSubtitle: { int32_t generation; CHECK(msg->findInt32("generation", &generation)); if (generation != mSendSubtitleGeneration) { // Drop obsolete msg. break; } // If current time doesn't reach to the fire time, // re-post the message with the adjusted delay time. int64_t fireTimeUs = kInvalidTimeUs; if (msg->findInt64("fireTimeUs", &fireTimeUs)) { // TODO: check if fireTimeUs is not kInvalidTimeUs. int64_t delayUs = delayUsFromCurrentTime(fireTimeUs); if (delayUs > 0) { msg->post(delayUs); break; } } sp obj; if (msg->findObject("subtitle", &obj)) { sp parcelEvent; parcelEvent = static_cast(obj.get()); notifyListener(&(parcelEvent->parcel)); doRead(); } else { notifyListener(); } break; } case kWhatSetSource: { mSendSubtitleGeneration++; sp obj; msg->findObject("source", &obj); if (mSource != NULL) { mSource->stop(); mSource.clear(); mSource = NULL; } // null source means deselect track. if (obj == NULL) { mPendingSeekTimeUs = kInvalidTimeUs; mPaused = false; notifyListener(); break; } mSource = static_cast(obj.get()); status_t err = mSource->start(); if (err != OK) { notifyError(err); break; } Parcel parcel; err = mSource->extractGlobalDescriptions(&parcel); if (err != OK) { notifyError(err); break; } notifyListener(&parcel); break; } } } void TimedTextPlayer::doSeekAndRead(int64_t seekTimeUs) { MediaSource::ReadOptions options; options.setSeekTo(seekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); doRead(&options); } void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) { int64_t startTimeUs = 0; int64_t endTimeUs = 0; sp parcelEvent = new ParcelEvent(); CHECK(mSource != NULL); status_t err = mSource->read(&startTimeUs, &endTimeUs, &(parcelEvent->parcel), options); if (err == WOULD_BLOCK) { sp msg = new AMessage(kWhatRetryRead, id()); if (options != NULL) { int64_t seekTimeUs = kInvalidTimeUs; MediaSource::ReadOptions::SeekMode seekMode = MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC; CHECK(options->getSeekTo(&seekTimeUs, &seekMode)); msg->setInt64("seekTimeUs", seekTimeUs); msg->setInt32("seekMode", seekMode); } msg->setInt32("generation", mSendSubtitleGeneration); msg->post(kWaitTimeUsToRetryRead); return; } else if (err != OK) { notifyError(err); return; } postTextEvent(parcelEvent, startTimeUs); if (endTimeUs > 0) { CHECK_GE(endTimeUs, startTimeUs); // send an empty timed text to clear the subtitle when it reaches to the // end time. postTextEvent(NULL, endTimeUs); } } void TimedTextPlayer::postTextEvent(const sp& parcel, int64_t timeUs) { int64_t delayUs = delayUsFromCurrentTime(timeUs); sp msg = new AMessage(kWhatSendSubtitle, id()); msg->setInt32("generation", mSendSubtitleGeneration); if (parcel != NULL) { msg->setObject("subtitle", parcel); } msg->setInt64("fireTimeUs", timeUs); msg->post(delayUs); } int64_t TimedTextPlayer::delayUsFromCurrentTime(int64_t fireTimeUs) { sp listener = mListener.promote(); if (listener == NULL) { // TODO: it may be better to return kInvalidTimeUs ALOGE("%s: Listener is NULL. (fireTimeUs = %" PRId64" )", __FUNCTION__, fireTimeUs); return 0; } int32_t positionMs = 0; listener->getCurrentPosition(&positionMs); int64_t positionUs = positionMs * 1000ll; if (fireTimeUs <= positionUs + kAdjustmentProcessingTimeUs) { return 0; } else { int64_t delayUs = fireTimeUs - positionUs - kAdjustmentProcessingTimeUs; if (delayUs > kMaxDelayUs) { return kMaxDelayUs; } return delayUs; } } void TimedTextPlayer::notifyError(int error) { sp listener = mListener.promote(); if (listener == NULL) { ALOGE("%s(error=%d): Listener is NULL.", __FUNCTION__, error); return; } listener->sendEvent(MEDIA_INFO, MEDIA_INFO_TIMED_TEXT_ERROR, error); } void TimedTextPlayer::notifyListener(const Parcel *parcel) { sp listener = mListener.promote(); if (listener == NULL) { ALOGE("%s: Listener is NULL.", __FUNCTION__); return; } if (parcel != NULL && (parcel->dataSize() > 0)) { listener->sendEvent(MEDIA_TIMED_TEXT, 0, 0, parcel); } else { // send an empty timed text to clear the screen listener->sendEvent(MEDIA_TIMED_TEXT); } } } // namespace android