blob: aecf6662424ff4e195068497880046b61edf435f [file] [log] [blame]
/*
* 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 <utils/Log.h>
#include <inttypes.h>
#include <limits.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/timedtext/TimedTextDriver.h>
#include <media/stagefright/MediaErrors.h>
#include <media/MediaPlayerInterface.h>
#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<MediaPlayerBase> &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, this))->post();
}
void TimedTextPlayer::pause() {
(new AMessage(kWhatPause, this))->post();
}
void TimedTextPlayer::resume() {
(new AMessage(kWhatResume, this))->post();
}
void TimedTextPlayer::seekToAsync(int64_t timeUs) {
sp<AMessage> msg = new AMessage(kWhatSeek, this);
msg->setInt64("seekTimeUs", timeUs);
msg->post();
}
void TimedTextPlayer::setDataSource(sp<TimedTextSource> source) {
sp<AMessage> msg = new AMessage(kWhatSetSource, this);
msg->setObject("source", source);
msg->post();
}
void TimedTextPlayer::onMessageReceived(const sp<AMessage> &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<MediaPlayerBase> 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<MediaSource::ReadOptions::SeekMode>(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<MediaPlayerBase> 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<RefBase> obj;
if (msg->findObject("subtitle", &obj)) {
sp<ParcelEvent> parcelEvent;
parcelEvent = static_cast<ParcelEvent*>(obj.get());
notifyListener(&(parcelEvent->parcel));
doRead();
} else {
notifyListener();
}
break;
}
case kWhatSetSource: {
mSendSubtitleGeneration++;
sp<RefBase> 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<TimedTextSource*>(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> parcelEvent = new ParcelEvent();
CHECK(mSource != NULL);
status_t err = mSource->read(&startTimeUs, &endTimeUs,
&(parcelEvent->parcel), options);
if (err == WOULD_BLOCK) {
sp<AMessage> msg = new AMessage(kWhatRetryRead, this);
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<ParcelEvent>& parcel, int64_t timeUs) {
int64_t delayUs = delayUsFromCurrentTime(timeUs);
sp<AMessage> msg = new AMessage(kWhatSendSubtitle, this);
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<MediaPlayerBase> 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<MediaPlayerBase> 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<MediaPlayerBase> 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