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