1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "NextFrameSeekTask.h"
8 #include "MediaDecoderReaderWrapper.h"
9 #include "mozilla/AbstractThread.h"
10 #include "mozilla/Assertions.h"
11 #include "nsPrintfCString.h"
12 
13 namespace mozilla {
14 
15 extern LazyLogModule gMediaSampleLog;
16 
17 #define SAMPLE_LOG(x, ...) MOZ_LOG(gMediaSampleLog, LogLevel::Debug, \
18   ("[NextFrameSeekTask] Decoder=%p " x, mDecoderID, ##__VA_ARGS__))
19 
20 namespace media {
21 
NextFrameSeekTask(const void * aDecoderID,AbstractThread * aThread,MediaDecoderReaderWrapper * aReader,const SeekTarget & aTarget,const MediaInfo & aInfo,const media::TimeUnit & aDuration,int64_t aCurrentTime,MediaQueue<MediaData> & aAudioQueue,MediaQueue<MediaData> & aVideoQueue)22 NextFrameSeekTask::NextFrameSeekTask(const void* aDecoderID,
23                                      AbstractThread* aThread,
24                                      MediaDecoderReaderWrapper* aReader,
25                                      const SeekTarget& aTarget,
26                                      const MediaInfo& aInfo,
27                                      const media::TimeUnit& aDuration,
28                                      int64_t aCurrentTime,
29                                      MediaQueue<MediaData>& aAudioQueue,
30                                      MediaQueue<MediaData>& aVideoQueue)
31   : SeekTask(aDecoderID, aThread, aReader, aTarget)
32   , mAudioQueue(aAudioQueue)
33   , mVideoQueue(aVideoQueue)
34   , mCurrentTime(aCurrentTime)
35   , mDuration(aDuration)
36 {
37   AssertOwnerThread();
38   MOZ_ASSERT(aInfo.HasVideo());
39 
40   // Configure MediaDecoderReaderWrapper.
41   SetCallbacks();
42 }
43 
~NextFrameSeekTask()44 NextFrameSeekTask::~NextFrameSeekTask()
45 {
46   AssertOwnerThread();
47   MOZ_ASSERT(mIsDiscarded);
48 }
49 
50 void
Discard()51 NextFrameSeekTask::Discard()
52 {
53   AssertOwnerThread();
54 
55   // Disconnect MDSM.
56   RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
57 
58   // Disconnect MediaDecoderReader.
59   CancelCallbacks();
60 
61   mIsDiscarded = true;
62 }
63 
64 bool
NeedToResetMDSM() const65 NextFrameSeekTask::NeedToResetMDSM() const
66 {
67   AssertOwnerThread();
68   return false;
69 }
70 
71 /*
72  * Remove samples from the queue until aCompare() returns false.
73  * aCompare A function object with the signature bool(int64_t) which returns
74  *          true for samples that should be removed.
75  */
76 template <typename Function> static void
DiscardFrames(MediaQueue<MediaData> & aQueue,const Function & aCompare)77 DiscardFrames(MediaQueue<MediaData>& aQueue, const Function& aCompare)
78 {
79   while(aQueue.GetSize() > 0) {
80     if (aCompare(aQueue.PeekFront()->mTime)) {
81       RefPtr<MediaData> releaseMe = aQueue.PopFront();
82       continue;
83     }
84     break;
85   }
86 }
87 
88 RefPtr<NextFrameSeekTask::SeekTaskPromise>
Seek(const media::TimeUnit &)89 NextFrameSeekTask::Seek(const media::TimeUnit&)
90 {
91   AssertOwnerThread();
92 
93   auto currentTime = mCurrentTime;
94   DiscardFrames(mVideoQueue, [currentTime] (int64_t aSampleTime) {
95     return aSampleTime <= currentTime;
96   });
97 
98   RefPtr<SeekTaskPromise> promise = mSeekTaskPromise.Ensure(__func__);
99   if (!IsVideoRequestPending() && NeedMoreVideo()) {
100     RequestVideoData();
101   }
102   MaybeFinishSeek(); // Might resolve mSeekTaskPromise and modify audio queue.
103   return promise;
104 }
105 
106 void
RequestVideoData()107 NextFrameSeekTask::RequestVideoData()
108 {
109   AssertOwnerThread();
110   mReader->RequestVideoData(false, media::TimeUnit());
111 }
112 
113 bool
NeedMoreVideo() const114 NextFrameSeekTask::NeedMoreVideo() const
115 {
116   AssertOwnerThread();
117   // Need to request video when we have none and video queue is not finished.
118   return mVideoQueue.GetSize() == 0 &&
119          !mSeekedVideoData &&
120          !mVideoQueue.IsFinished() &&
121          !mIsVideoQueueFinished;
122 }
123 
124 bool
IsVideoRequestPending() const125 NextFrameSeekTask::IsVideoRequestPending() const
126 {
127   AssertOwnerThread();
128   return mReader->IsRequestingVideoData() || mReader->IsWaitingVideoData();
129 }
130 
131 bool
IsAudioSeekComplete() const132 NextFrameSeekTask::IsAudioSeekComplete() const
133 {
134   AssertOwnerThread();
135   // Don't finish seek until there are no pending requests. Otherwise, we might
136   // lose audio samples for the promise is resolved asynchronously.
137   return !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData();
138 }
139 
140 bool
IsVideoSeekComplete() const141 NextFrameSeekTask::IsVideoSeekComplete() const
142 {
143   AssertOwnerThread();
144   // Don't finish seek until there are no pending requests. Otherwise, we might
145   // lose video samples for the promise is resolved asynchronously.
146   return !IsVideoRequestPending() && !NeedMoreVideo();
147 }
148 
149 void
MaybeFinishSeek()150 NextFrameSeekTask::MaybeFinishSeek()
151 {
152   AssertOwnerThread();
153   if (IsAudioSeekComplete() && IsVideoSeekComplete()) {
154     UpdateSeekTargetTime();
155 
156     auto time = mTarget.GetTime().ToMicroseconds();
157     DiscardFrames(mAudioQueue, [time] (int64_t aSampleTime) {
158       return aSampleTime < time;
159     });
160 
161     Resolve(__func__); // Call to MDSM::SeekCompleted();
162   }
163 }
164 
165 void
OnAudioDecoded(MediaData * aAudioSample)166 NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample)
167 {
168   AssertOwnerThread();
169   MOZ_ASSERT(aAudioSample);
170   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
171 
172   // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
173   // resolved.
174 
175   SAMPLE_LOG("OnAudioDecoded [%lld,%lld]",
176              aAudioSample->mTime,
177              aAudioSample->GetEndTime());
178 
179   // We accept any audio data here.
180   mSeekedAudioData = aAudioSample;
181 
182   MaybeFinishSeek();
183 }
184 
185 void
OnAudioNotDecoded(const MediaResult & aError)186 NextFrameSeekTask::OnAudioNotDecoded(const MediaResult& aError)
187 {
188   AssertOwnerThread();
189   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
190 
191   SAMPLE_LOG("OnAudioNotDecoded (aError=%u)", aError.Code());
192 
193   // We don't really handle audio deocde error here. Let MDSM to trigger further
194   // audio decoding tasks if it needs to play audio, and MDSM will then receive
195   // the decoding state from MediaDecoderReader.
196 
197   MaybeFinishSeek();
198 }
199 
200 void
OnVideoDecoded(MediaData * aVideoSample)201 NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample)
202 {
203   AssertOwnerThread();
204   MOZ_ASSERT(aVideoSample);
205   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
206 
207   // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
208   // resolved.
209 
210   SAMPLE_LOG("OnVideoDecoded [%lld,%lld]",
211              aVideoSample->mTime,
212              aVideoSample->GetEndTime());
213 
214   if (aVideoSample->mTime > mCurrentTime) {
215     mSeekedVideoData = aVideoSample;
216   }
217 
218   if (NeedMoreVideo()) {
219     RequestVideoData();
220     return;
221   }
222 
223   MaybeFinishSeek();
224 }
225 
226 void
OnVideoNotDecoded(const MediaResult & aError)227 NextFrameSeekTask::OnVideoNotDecoded(const MediaResult& aError)
228 {
229   AssertOwnerThread();
230   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
231 
232   SAMPLE_LOG("OnVideoNotDecoded (aError=%u)", aError.Code());
233 
234   if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
235     mIsVideoQueueFinished = true;
236   }
237 
238   // Video seek not finished.
239   if (NeedMoreVideo()) {
240     switch (aError.Code()) {
241       case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
242         mReader->WaitForData(MediaData::VIDEO_DATA);
243         break;
244       case NS_ERROR_DOM_MEDIA_CANCELED:
245         RequestVideoData();
246         break;
247       case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
248         MOZ_ASSERT(false, "Shouldn't want more data for ended video.");
249         break;
250       default:
251         // We might lose the audio sample after canceling the callbacks.
252         // However it doesn't really matter because MDSM is gonna shut down
253         // when seek fails.
254         CancelCallbacks();
255         // Reject the promise since we can't finish video seek anyway.
256         RejectIfExist(aError, __func__);
257         break;
258     }
259     return;
260   }
261 
262   MaybeFinishSeek();
263 }
264 
265 void
SetCallbacks()266 NextFrameSeekTask::SetCallbacks()
267 {
268   AssertOwnerThread();
269 
270   // Register dummy callbcak for audio decoding since we don't need to handle
271   // the decoded audio samples.
272   RefPtr<NextFrameSeekTask> self = this;
273   mAudioCallback = mReader->AudioCallback().Connect(
274     OwnerThread(), [self] (AudioCallbackData aData) {
275     if (aData.is<MediaData*>()) {
276       self->OnAudioDecoded(aData.as<MediaData*>());
277     } else {
278       self->OnAudioNotDecoded(aData.as<MediaResult>());
279     }
280   });
281 
282   mVideoCallback = mReader->VideoCallback().Connect(
283     OwnerThread(), [self] (VideoCallbackData aData) {
284     typedef Tuple<MediaData*, TimeStamp> Type;
285     if (aData.is<Type>()) {
286       self->OnVideoDecoded(Get<0>(aData.as<Type>()));
287     } else {
288       self->OnVideoNotDecoded(aData.as<MediaResult>());
289     }
290   });
291 
292   mAudioWaitCallback = mReader->AudioWaitCallback().Connect(
293     OwnerThread(), [self] (WaitCallbackData aData) {
294     // We don't make an audio decode request here, instead, let MDSM to
295     // trigger further audio decode tasks if MDSM itself needs to play audio.
296     self->MaybeFinishSeek();
297   });
298 
299   mVideoWaitCallback = mReader->VideoWaitCallback().Connect(
300     OwnerThread(), [self] (WaitCallbackData aData) {
301     if (self->NeedMoreVideo()) {
302       if (aData.is<MediaData::Type>()) {
303         self->RequestVideoData();
304       } else {
305         // Reject if we can't finish video seeking.
306         self->CancelCallbacks();
307         self->RejectIfExist(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
308       }
309       return;
310     }
311     self->MaybeFinishSeek();
312   });
313 }
314 
315 void
CancelCallbacks()316 NextFrameSeekTask::CancelCallbacks()
317 {
318   AssertOwnerThread();
319   mAudioCallback.DisconnectIfExists();
320   mVideoCallback.DisconnectIfExists();
321   mAudioWaitCallback.DisconnectIfExists();
322   mVideoWaitCallback.DisconnectIfExists();
323 }
324 
325 void
UpdateSeekTargetTime()326 NextFrameSeekTask::UpdateSeekTargetTime()
327 {
328   AssertOwnerThread();
329 
330   RefPtr<MediaData> data = mVideoQueue.PeekFront();
331   if (data) {
332     mTarget.SetTime(TimeUnit::FromMicroseconds(data->mTime));
333   } else if (mSeekedVideoData) {
334     mTarget.SetTime(TimeUnit::FromMicroseconds(mSeekedVideoData->mTime));
335   } else if (mIsVideoQueueFinished || mVideoQueue.AtEndOfStream()) {
336     mTarget.SetTime(mDuration);
337   } else {
338     MOZ_ASSERT(false, "No data!");
339   }
340 }
341 } // namespace media
342 } // namespace mozilla
343