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