Source code

Revision control

Other Tools

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 <algorithm>
8
#include <stdint.h>
9
#include <utility>
10
11
#include "mediasink/AudioSink.h"
12
#include "mediasink/AudioSinkWrapper.h"
13
#include "mediasink/DecodedStream.h"
14
#include "mediasink/VideoSink.h"
15
#include "mozilla/Logging.h"
16
#include "mozilla/MathAlgorithms.h"
17
#include "mozilla/NotNull.h"
18
#include "mozilla/SharedThreadPool.h"
19
#include "mozilla/Sprintf.h"
20
#include "mozilla/StaticPrefs_media.h"
21
#include "mozilla/Telemetry.h"
22
#include "mozilla/TaskQueue.h"
23
#include "mozilla/Tuple.h"
24
#include "nsIMemoryReporter.h"
25
#include "nsPrintfCString.h"
26
#include "nsTArray.h"
27
#include "AudioSegment.h"
28
#include "DOMMediaStream.h"
29
#include "ImageContainer.h"
30
#include "MediaDecoder.h"
31
#include "MediaDecoderStateMachine.h"
32
#include "MediaShutdownManager.h"
33
#include "MediaTrackGraph.h"
34
#include "MediaTimer.h"
35
#include "ReaderProxy.h"
36
#include "TimeUnits.h"
37
#include "VideoSegment.h"
38
#include "VideoUtils.h"
39
40
namespace mozilla {
41
42
using namespace mozilla::media;
43
44
#define NS_DispatchToMainThread(...) \
45
CompileError_UseAbstractThreadDispatchInstead
46
47
// avoid redefined macro in unified build
48
#undef FMT
49
#undef LOG
50
#undef LOGV
51
#undef LOGW
52
#undef LOGE
53
#undef SFMT
54
#undef SLOG
55
#undef SLOGW
56
#undef SLOGE
57
58
#define FMT(x, ...) "Decoder=%p " x, mDecoderID, ##__VA_ARGS__
59
#define LOG(x, ...) \
60
DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, "Decoder=%p " x, mDecoderID, \
61
##__VA_ARGS__)
62
#define LOGV(x, ...) \
63
DDMOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, "Decoder=%p " x, mDecoderID, \
64
##__VA_ARGS__)
65
#define LOGW(x, ...) NS_WARNING(nsPrintfCString(FMT(x, ##__VA_ARGS__)).get())
66
#define LOGE(x, ...) \
67
NS_DebugBreak(NS_DEBUG_WARNING, \
68
nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, \
69
__FILE__, __LINE__)
70
71
// Used by StateObject and its sub-classes
72
#define SFMT(x, ...) \
73
"Decoder=%p state=%s " x, mMaster->mDecoderID, ToStateStr(GetState()), \
74
##__VA_ARGS__
75
#define SLOG(x, ...) \
76
DDMOZ_LOGEX(mMaster, gMediaDecoderLog, LogLevel::Debug, "state=%s " x, \
77
ToStateStr(GetState()), ##__VA_ARGS__)
78
#define SLOGW(x, ...) NS_WARNING(nsPrintfCString(SFMT(x, ##__VA_ARGS__)).get())
79
#define SLOGE(x, ...) \
80
NS_DebugBreak(NS_DEBUG_WARNING, \
81
nsPrintfCString(SFMT(x, ##__VA_ARGS__)).get(), nullptr, \
82
__FILE__, __LINE__)
83
84
// Certain constants get stored as member variables and then adjusted by various
85
// scale factors on a per-decoder basis. We want to make sure to avoid using
86
// these constants directly, so we put them in a namespace.
87
namespace detail {
88
89
// Resume a suspended video decoder to the current playback position plus this
90
// time premium for compensating the seeking delay.
91
static constexpr auto RESUME_VIDEO_PREMIUM = TimeUnit::FromMicroseconds(125000);
92
93
static const int64_t AMPLE_AUDIO_USECS = 2000000;
94
95
// If more than this much decoded audio is queued, we'll hold off
96
// decoding more audio.
97
static constexpr auto AMPLE_AUDIO_THRESHOLD =
98
TimeUnit::FromMicroseconds(AMPLE_AUDIO_USECS);
99
100
} // namespace detail
101
102
// If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
103
// we're not "prerolling video", we'll skip the video up to the next keyframe
104
// which is at or after the current playback position.
105
static const uint32_t LOW_VIDEO_FRAMES = 2;
106
107
// Arbitrary "frame duration" when playing only audio.
108
static const int AUDIO_DURATION_USECS = 40000;
109
110
namespace detail {
111
112
// If we have less than this much buffered data available, we'll consider
113
// ourselves to be running low on buffered data. We determine how much
114
// buffered data we have remaining using the reader's GetBuffered()
115
// implementation.
116
static const int64_t LOW_BUFFER_THRESHOLD_USECS = 5000000;
117
118
static constexpr auto LOW_BUFFER_THRESHOLD =
119
TimeUnit::FromMicroseconds(LOW_BUFFER_THRESHOLD_USECS);
120
121
// LOW_BUFFER_THRESHOLD_USECS needs to be greater than AMPLE_AUDIO_USECS,
122
// otherwise the skip-to-keyframe logic can activate when we're running low on
123
// data.
124
static_assert(LOW_BUFFER_THRESHOLD_USECS > AMPLE_AUDIO_USECS,
125
"LOW_BUFFER_THRESHOLD_USECS is too small");
126
127
} // namespace detail
128
129
// Amount of excess data to add in to the "should we buffer" calculation.
130
static constexpr auto EXHAUSTED_DATA_MARGIN =
131
TimeUnit::FromMicroseconds(100000);
132
133
static const uint32_t MIN_VIDEO_QUEUE_SIZE = 3;
134
static const uint32_t MAX_VIDEO_QUEUE_SIZE = 10;
135
#ifdef MOZ_APPLEMEDIA
136
static const uint32_t HW_VIDEO_QUEUE_SIZE = 10;
137
#else
138
static const uint32_t HW_VIDEO_QUEUE_SIZE = 3;
139
#endif
140
static const uint32_t VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE = 9999;
141
142
static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
143
static uint32_t sVideoQueueHWAccelSize = HW_VIDEO_QUEUE_SIZE;
144
static uint32_t sVideoQueueSendToCompositorSize =
145
VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE;
146
147
static void InitVideoQueuePrefs() {
148
MOZ_ASSERT(NS_IsMainThread());
149
static bool sPrefInit = false;
150
if (!sPrefInit) {
151
sPrefInit = true;
152
sVideoQueueDefaultSize = Preferences::GetUint(
153
"media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE);
154
sVideoQueueHWAccelSize = Preferences::GetUint(
155
"media.video-queue.hw-accel-size", HW_VIDEO_QUEUE_SIZE);
156
sVideoQueueSendToCompositorSize =
157
Preferences::GetUint("media.video-queue.send-to-compositor-size",
158
VIDEO_QUEUE_SEND_TO_COMPOSITOR_SIZE);
159
}
160
}
161
162
template <typename Type, typename Function>
163
static void DiscardFramesFromTail(MediaQueue<Type>& aQueue,
164
const Function&& aTest) {
165
while (aQueue.GetSize()) {
166
if (aTest(aQueue.PeekBack()->mTime.ToMicroseconds())) {
167
RefPtr<Type> releaseMe = aQueue.PopBack();
168
continue;
169
}
170
break;
171
}
172
}
173
174
// Delay, in milliseconds, that tabs needs to be in background before video
175
// decoding is suspended.
176
static TimeDuration SuspendBackgroundVideoDelay() {
177
return TimeDuration::FromMilliseconds(
178
StaticPrefs::media_suspend_bkgnd_video_delay_ms());
179
}
180
181
class MediaDecoderStateMachine::StateObject {
182
public:
183
virtual ~StateObject() {}
184
virtual void Exit() {} // Exit action.
185
virtual void Step() {} // Perform a 'cycle' of this state object.
186
virtual State GetState() const = 0;
187
188
// Event handlers for various events.
189
virtual void HandleAudioCaptured() {}
190
virtual void HandleAudioDecoded(AudioData* aAudio) {
191
Crash("Unexpected event!", __func__);
192
}
193
virtual void HandleVideoDecoded(VideoData* aVideo, TimeStamp aDecodeStart) {
194
Crash("Unexpected event!", __func__);
195
}
196
virtual void HandleAudioWaited(MediaData::Type aType) {
197
Crash("Unexpected event!", __func__);
198
}
199
virtual void HandleVideoWaited(MediaData::Type aType) {
200
Crash("Unexpected event!", __func__);
201
}
202
virtual void HandleWaitingForAudio() { Crash("Unexpected event!", __func__); }
203
virtual void HandleAudioCanceled() { Crash("Unexpected event!", __func__); }
204
virtual void HandleEndOfAudio() { Crash("Unexpected event!", __func__); }
205
virtual void HandleWaitingForVideo() { Crash("Unexpected event!", __func__); }
206
virtual void HandleVideoCanceled() { Crash("Unexpected event!", __func__); }
207
virtual void HandleEndOfVideo() { Crash("Unexpected event!", __func__); }
208
209
virtual RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget);
210
211
virtual RefPtr<ShutdownPromise> HandleShutdown();
212
213
virtual void HandleVideoSuspendTimeout() = 0;
214
215
virtual void HandleResumeVideoDecoding(const TimeUnit& aTarget);
216
217
virtual void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) {}
218
219
virtual void GetDebugInfo(
220
dom::MediaDecoderStateMachineDecodingStateDebugInfo& aInfo) {}
221
222
virtual void HandleLoopingChanged() {}
223
224
private:
225
template <class S, typename R, typename... As>
226
auto ReturnTypeHelper(R (S::*)(As...)) -> R;
227
228
void Crash(const char* aReason, const char* aSite) {
229
char buf[1024];
230
SprintfLiteral(buf, "%s state=%s callsite=%s", aReason,
231
ToStateStr(GetState()), aSite);
232
MOZ_ReportAssertionFailure(buf, __FILE__, __LINE__);
233
MOZ_CRASH();
234
}
235
236
protected:
237
enum class EventVisibility : int8_t { Observable, Suppressed };
238
239
using Master = MediaDecoderStateMachine;
240
explicit StateObject(Master* aPtr) : mMaster(aPtr) {}
241
TaskQueue* OwnerThread() const { return mMaster->mTaskQueue; }
242
ReaderProxy* Reader() const { return mMaster->mReader; }
243
const MediaInfo& Info() const { return mMaster->Info(); }
244
MediaQueue<AudioData>& AudioQueue() const { return mMaster->mAudioQueue; }
245
MediaQueue<VideoData>& VideoQueue() const { return mMaster->mVideoQueue; }
246
247
template <class S, typename... Args, size_t... Indexes>
248
auto CallEnterMemberFunction(S* aS, Tuple<Args...>& aTuple,
249
std::index_sequence<Indexes...>)
250
-> decltype(ReturnTypeHelper(&S::Enter)) {
251
return aS->Enter(std::move(Get<Indexes>(aTuple))...);
252
}
253
254
// Note this function will delete the current state object.
255
// Don't access members to avoid UAF after this call.
256
template <class S, typename... Ts>
257
auto SetState(Ts&&... aArgs) -> decltype(ReturnTypeHelper(&S::Enter)) {
258
// |aArgs| must be passed by reference to avoid passing MOZ_NON_PARAM class
259
// SeekJob by value. See bug 1287006 and bug 1338374. But we still *must*
260
// copy the parameters, because |Exit()| can modify them. See bug 1312321.
261
// So we 1) pass the parameters by reference, but then 2) immediately copy
262
// them into a Tuple to be safe against modification, and finally 3) move
263
// the elements of the Tuple into the final function call.
264
auto copiedArgs = MakeTuple(std::forward<Ts>(aArgs)...);
265
266
// Copy mMaster which will reset to null.
267
auto master = mMaster;
268
269
auto* s = new S(master);
270
271
// It's possible to seek again during seeking, otherwise the new state
272
// should always be different from the original one.
273
MOZ_ASSERT(GetState() != s->GetState() ||
274
GetState() == DECODER_STATE_SEEKING_ACCURATE ||
275
GetState() == DECODER_STATE_SEEKING_FROMDORMANT ||
276
GetState() == DECODER_STATE_SEEKING_NEXTFRAMESEEKING ||
277
GetState() == DECODER_STATE_SEEKING_VIDEOONLY);
278
279
SLOG("change state to: %s", ToStateStr(s->GetState()));
280
281
Exit();
282
283
// Delete the old state asynchronously to avoid UAF if the caller tries to
284
// access its members after SetState() returns.
285
master->OwnerThread()->DispatchDirectTask(
286
NS_NewRunnableFunction("MDSM::StateObject::DeleteOldState",
287
[toDelete = std::move(master->mStateObj)]() {}));
288
// Also reset mMaster to catch potentail UAF.
289
mMaster = nullptr;
290
291
master->mStateObj.reset(s);
292
return CallEnterMemberFunction(s, copiedArgs,
293
std::index_sequence_for<Ts...>{});
294
}
295
296
RefPtr<MediaDecoder::SeekPromise> SetSeekingState(
297
SeekJob&& aSeekJob, EventVisibility aVisibility);
298
299
void SetDecodingState();
300
301
// Take a raw pointer in order not to change the life cycle of MDSM.
302
// It is guaranteed to be valid by MDSM.
303
Master* mMaster;
304
};
305
306
/**
307
* Purpose: decode metadata like duration and dimensions of the media resource.
308
*
309
* Transition to other states when decoding metadata is done:
310
* SHUTDOWN if failing to decode metadata.
311
* DECODING_FIRSTFRAME otherwise.
312
*/
313
class MediaDecoderStateMachine::DecodeMetadataState
314
: public MediaDecoderStateMachine::StateObject {
315
public:
316
explicit DecodeMetadataState(Master* aPtr) : StateObject(aPtr) {}
317
318
void Enter() {
319
MOZ_ASSERT(!mMaster->mVideoDecodeSuspended);
320
MOZ_ASSERT(!mMetadataRequest.Exists());
321
SLOG("Dispatching AsyncReadMetadata");
322
323
// We disconnect mMetadataRequest in Exit() so it is fine to capture
324
// a raw pointer here.
325
Reader()
326
->ReadMetadata()
327
->Then(
328
OwnerThread(), __func__,
329
[this](MetadataHolder&& aMetadata) {
330
OnMetadataRead(std::move(aMetadata));
331
},
332
[this](const MediaResult& aError) { OnMetadataNotRead(aError); })
333
->Track(mMetadataRequest);
334
}
335
336
void Exit() override { mMetadataRequest.DisconnectIfExists(); }
337
338
State GetState() const override { return DECODER_STATE_DECODING_METADATA; }
339
340
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override {
341
MOZ_DIAGNOSTIC_ASSERT(false, "Can't seek while decoding metadata.");
342
return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
343
}
344
345
void HandleVideoSuspendTimeout() override {
346
// Do nothing since no decoders are created yet.
347
}
348
349
void HandleResumeVideoDecoding(const TimeUnit&) override {
350
// We never suspend video decoding in this state.
351
MOZ_ASSERT(false, "Shouldn't have suspended video decoding.");
352
}
353
354
private:
355
void OnMetadataRead(MetadataHolder&& aMetadata);
356
357
void OnMetadataNotRead(const MediaResult& aError) {
358
mMetadataRequest.Complete();
359
SLOGE("Decode metadata failed, shutting down decoder");
360
mMaster->DecodeError(aError);
361
}
362
363
MozPromiseRequestHolder<MediaFormatReader::MetadataPromise> mMetadataRequest;
364
};
365
366
/**
367
* Purpose: release decoder resources to save memory and hardware resources.
368
*
369
* Transition to:
370
* SEEKING if any seek request or play state changes to PLAYING.
371
*/
372
class MediaDecoderStateMachine::DormantState
373
: public MediaDecoderStateMachine::StateObject {
374
public:
375
explicit DormantState(Master* aPtr) : StateObject(aPtr) {}
376
377
void Enter() {
378
if (mMaster->IsPlaying()) {
379
mMaster->StopPlayback();
380
}
381
382
// Calculate the position to seek to when exiting dormant.
383
auto t = mMaster->mMediaSink->IsStarted() ? mMaster->GetClock()
384
: mMaster->GetMediaTime();
385
mMaster->AdjustByLooping(t);
386
mPendingSeek.mTarget.emplace(t, SeekTarget::Accurate);
387
// SeekJob asserts |mTarget.IsValid() == !mPromise.IsEmpty()| so we
388
// need to create the promise even it is not used at all.
389
// The promise may be used when coming out of DormantState into
390
// SeekingState.
391
RefPtr<MediaDecoder::SeekPromise> x =
392
mPendingSeek.mPromise.Ensure(__func__);
393
394
// No need to call ResetDecode() and StopMediaSink() here.
395
// We will do them during seeking when exiting dormant.
396
397
// Ignore WAIT_FOR_DATA since we won't decode in dormant.
398
mMaster->mAudioWaitRequest.DisconnectIfExists();
399
mMaster->mVideoWaitRequest.DisconnectIfExists();
400
401
MaybeReleaseResources();
402
}
403
404
void Exit() override {
405
// mPendingSeek is either moved when exiting dormant or
406
// should be rejected here before transition to SHUTDOWN.
407
mPendingSeek.RejectIfExists(__func__);
408
}
409
410
State GetState() const override { return DECODER_STATE_DORMANT; }
411
412
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override;
413
414
void HandleVideoSuspendTimeout() override {
415
// Do nothing since we've released decoders in Enter().
416
}
417
418
void HandleResumeVideoDecoding(const TimeUnit&) override {
419
// Do nothing since we won't resume decoding until exiting dormant.
420
}
421
422
void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override;
423
424
void HandleAudioDecoded(AudioData*) override { MaybeReleaseResources(); }
425
void HandleVideoDecoded(VideoData*, TimeStamp) override {
426
MaybeReleaseResources();
427
}
428
void HandleWaitingForAudio() override { MaybeReleaseResources(); }
429
void HandleWaitingForVideo() override { MaybeReleaseResources(); }
430
void HandleAudioCanceled() override { MaybeReleaseResources(); }
431
void HandleVideoCanceled() override { MaybeReleaseResources(); }
432
void HandleEndOfAudio() override { MaybeReleaseResources(); }
433
void HandleEndOfVideo() override { MaybeReleaseResources(); }
434
435
private:
436
void MaybeReleaseResources() {
437
if (!mMaster->mAudioDataRequest.Exists() &&
438
!mMaster->mVideoDataRequest.Exists()) {
439
// Release decoders only when they are idle. Otherwise it might cause
440
// decode error later when resetting decoders during seeking.
441
mMaster->mReader->ReleaseResources();
442
}
443
}
444
445
SeekJob mPendingSeek;
446
};
447
448
/**
449
* Purpose: decode the 1st audio and video frames to fire the 'loadeddata'
450
* event.
451
*
452
* Transition to:
453
* SHUTDOWN if any decode error.
454
* SEEKING if any seek request.
455
* DECODING/LOOPING_DECODING when the 'loadeddata' event is fired.
456
*/
457
class MediaDecoderStateMachine::DecodingFirstFrameState
458
: public MediaDecoderStateMachine::StateObject {
459
public:
460
explicit DecodingFirstFrameState(Master* aPtr) : StateObject(aPtr) {}
461
462
void Enter();
463
464
void Exit() override {
465
// mPendingSeek is either moved in MaybeFinishDecodeFirstFrame()
466
// or should be rejected here before transition to SHUTDOWN.
467
mPendingSeek.RejectIfExists(__func__);
468
}
469
470
State GetState() const override { return DECODER_STATE_DECODING_FIRSTFRAME; }
471
472
void HandleAudioDecoded(AudioData* aAudio) override {
473
mMaster->PushAudio(aAudio);
474
MaybeFinishDecodeFirstFrame();
475
}
476
477
void HandleVideoDecoded(VideoData* aVideo, TimeStamp aDecodeStart) override {
478
mMaster->PushVideo(aVideo);
479
MaybeFinishDecodeFirstFrame();
480
}
481
482
void HandleWaitingForAudio() override {
483
mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
484
}
485
486
void HandleAudioCanceled() override { mMaster->RequestAudioData(); }
487
488
void HandleEndOfAudio() override {
489
AudioQueue().Finish();
490
MaybeFinishDecodeFirstFrame();
491
}
492
493
void HandleWaitingForVideo() override {
494
mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
495
}
496
497
void HandleVideoCanceled() override {
498
mMaster->RequestVideoData(media::TimeUnit());
499
}
500
501
void HandleEndOfVideo() override {
502
VideoQueue().Finish();
503
MaybeFinishDecodeFirstFrame();
504
}
505
506
void HandleAudioWaited(MediaData::Type aType) override {
507
mMaster->RequestAudioData();
508
}
509
510
void HandleVideoWaited(MediaData::Type aType) override {
511
mMaster->RequestVideoData(media::TimeUnit());
512
}
513
514
void HandleVideoSuspendTimeout() override {
515
// Do nothing for we need to decode the 1st video frame to get the
516
// dimensions.
517
}
518
519
void HandleResumeVideoDecoding(const TimeUnit&) override {
520
// We never suspend video decoding in this state.
521
MOZ_ASSERT(false, "Shouldn't have suspended video decoding.");
522
}
523
524
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override {
525
if (mMaster->mIsMSE) {
526
return StateObject::HandleSeek(aTarget);
527
}
528
// Delay seek request until decoding first frames for non-MSE media.
529
SLOG("Not Enough Data to seek at this stage, queuing seek");
530
mPendingSeek.RejectIfExists(__func__);
531
mPendingSeek.mTarget.emplace(aTarget);
532
return mPendingSeek.mPromise.Ensure(__func__);
533
}
534
535
private:
536
// Notify FirstFrameLoaded if having decoded first frames and
537
// transition to SEEKING if there is any pending seek, or DECODING otherwise.
538
void MaybeFinishDecodeFirstFrame();
539
540
SeekJob mPendingSeek;
541
};
542
543
/**
544
* Purpose: decode audio/video data for playback.
545
*
546
* Transition to:
547
* DORMANT if playback is paused for a while.
548
* SEEKING if any seek request.
549
* SHUTDOWN if any decode error.
550
* BUFFERING if playback can't continue due to lack of decoded data.
551
* COMPLETED when having decoded all audio/video data.
552
* LOOPING_DECODING when media start seamless looping
553
*/
554
class MediaDecoderStateMachine::DecodingState
555
: public MediaDecoderStateMachine::StateObject {
556
public:
557
explicit DecodingState(Master* aPtr)
558
: StateObject(aPtr), mDormantTimer(OwnerThread()) {}
559
560
void Enter();
561
562
void Exit() override {
563
if (!mDecodeStartTime.IsNull()) {
564
TimeDuration decodeDuration = TimeStamp::Now() - mDecodeStartTime;
565
SLOG("Exiting DECODING, decoded for %.3lfs", decodeDuration.ToSeconds());
566
}
567
mDormantTimer.Reset();
568
mOnAudioPopped.DisconnectIfExists();
569
mOnVideoPopped.DisconnectIfExists();
570
}
571
572
void Step() override;
573
574
State GetState() const override { return DECODER_STATE_DECODING; }
575
576
void HandleAudioDecoded(AudioData* aAudio) override {
577
mMaster->PushAudio(aAudio);
578
DispatchDecodeTasksIfNeeded();
579
MaybeStopPrerolling();
580
}
581
582
void HandleVideoDecoded(VideoData* aVideo, TimeStamp aDecodeStart) override {
583
mMaster->PushVideo(aVideo);
584
DispatchDecodeTasksIfNeeded();
585
MaybeStopPrerolling();
586
}
587
588
void HandleAudioCanceled() override { mMaster->RequestAudioData(); }
589
590
void HandleVideoCanceled() override {
591
mMaster->RequestVideoData(mMaster->GetMediaTime());
592
}
593
594
void HandleEndOfAudio() override;
595
void HandleEndOfVideo() override;
596
597
void HandleWaitingForAudio() override {
598
mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
599
MaybeStopPrerolling();
600
}
601
602
void HandleWaitingForVideo() override {
603
mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
604
MaybeStopPrerolling();
605
}
606
607
void HandleAudioWaited(MediaData::Type aType) override {
608
mMaster->RequestAudioData();
609
}
610
611
void HandleVideoWaited(MediaData::Type aType) override {
612
mMaster->RequestVideoData(mMaster->GetMediaTime());
613
}
614
615
void HandleAudioCaptured() override {
616
MaybeStopPrerolling();
617
// MediaSink is changed. Schedule Step() to check if we can start playback.
618
mMaster->ScheduleStateMachine();
619
}
620
621
void HandleVideoSuspendTimeout() override {
622
// No video, so nothing to suspend.
623
if (!mMaster->HasVideo()) {
624
return;
625
}
626
627
mMaster->mVideoDecodeSuspended = true;
628
mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::EnterVideoSuspend);
629
Reader()->SetVideoBlankDecode(true);
630
}
631
632
void HandlePlayStateChanged(MediaDecoder::PlayState aPlayState) override {
633
if (aPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
634
// Schedule Step() to check if we can start playback.
635
mMaster->ScheduleStateMachine();
636
// Try to dispatch decoding tasks for mMinimizePreroll might be reset.
637
DispatchDecodeTasksIfNeeded();
638
}
639
640
if (aPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
641
StartDormantTimer();
642
} else {
643
mDormantTimer.Reset();
644
}
645
}
646
647
void GetDebugInfo(
648
dom::MediaDecoderStateMachineDecodingStateDebugInfo& aInfo) override {
649
aInfo.mIsPrerolling = mIsPrerolling;
650
}
651
652
void HandleLoopingChanged() override { SetDecodingState(); }
653
654
protected:
655
virtual void EnsureAudioDecodeTaskQueued();
656
657
private:
658
void DispatchDecodeTasksIfNeeded();
659
void EnsureVideoDecodeTaskQueued();
660
void MaybeStartBuffering();
661
662
// At the start of decoding we want to "preroll" the decode until we've
663
// got a few frames decoded before we consider whether decode is falling
664
// behind. Otherwise our "we're falling behind" logic will trigger
665
// unnecessarily if we start playing as soon as the first sample is
666
// decoded. These two fields store how many video frames and audio
667
// samples we must consume before are considered to be finished prerolling.
668
TimeUnit AudioPrerollThreshold() const {
669
return mMaster->mAmpleAudioThreshold / 2;
670
}
671
672
uint32_t VideoPrerollFrames() const {
673
return mMaster->GetAmpleVideoFrames() / 2;
674
}
675
676
bool DonePrerollingAudio() {
677
return !mMaster->IsAudioDecoding() ||
678
mMaster->GetDecodedAudioDuration() >=
679
AudioPrerollThreshold().MultDouble(mMaster->mPlaybackRate);
680
}
681
682
bool DonePrerollingVideo() {
683
return !mMaster->IsVideoDecoding() ||
684
static_cast<uint32_t>(mMaster->VideoQueue().GetSize()) >=
685
VideoPrerollFrames() * mMaster->mPlaybackRate + 1;
686
}
687
688
void MaybeStopPrerolling() {
689
if (mIsPrerolling &&
690
(DonePrerollingAudio() || mMaster->IsWaitingAudioData()) &&
691
(DonePrerollingVideo() || mMaster->IsWaitingVideoData())) {
692
mIsPrerolling = false;
693
// Check if we can start playback.
694
mMaster->ScheduleStateMachine();
695
}
696
}
697
698
void StartDormantTimer() {
699
if (!mMaster->mMediaSeekable) {
700
// Don't enter dormant if the media is not seekable because we need to
701
// seek when exiting dormant.
702
return;
703
}
704
705
auto timeout = StaticPrefs::media_dormant_on_pause_timeout_ms();
706
if (timeout < 0) {
707
// Disabled when timeout is negative.
708
return;
709
} else if (timeout == 0) {
710
// Enter dormant immediately without scheduling a timer.
711
SetState<DormantState>();
712
return;
713
}
714
715
if (mMaster->mMinimizePreroll) {
716
SetState<DormantState>();
717
return;
718
}
719
720
TimeStamp target =
721
TimeStamp::Now() + TimeDuration::FromMilliseconds(timeout);
722
723
mDormantTimer.Ensure(
724
target,
725
[this]() {
726
mDormantTimer.CompleteRequest();
727
SetState<DormantState>();
728
},
729
[this]() { mDormantTimer.CompleteRequest(); });
730
}
731
732
// Time at which we started decoding.
733
TimeStamp mDecodeStartTime;
734
735
// When we start decoding (either for the first time, or after a pause)
736
// we may be low on decoded data. We don't want our "low data" logic to
737
// kick in and decide that we're low on decoded data because the download
738
// can't keep up with the decode, and cause us to pause playback. So we
739
// have a "preroll" stage, where we ignore the results of our "low data"
740
// logic during the first few frames of our decode. This occurs during
741
// playback.
742
bool mIsPrerolling = true;
743
744
// Fired when playback is paused for a while to enter dormant.
745
DelayedScheduler mDormantTimer;
746
747
MediaEventListener mOnAudioPopped;
748
MediaEventListener mOnVideoPopped;
749
};
750
751
/**
752
* Purpose: decode audio/video data for playback when media is in seamless
753
* looping, we will adjust media time to make samples time monotonically
754
* increasing.
755
*
756
* Transition to:
757
* DORMANT if playback is paused for a while.
758
* SEEKING if any seek request.
759
* SHUTDOWN if any decode error.
760
* BUFFERING if playback can't continue due to lack of decoded data.
761
* COMPLETED when having decoded all audio/video data.
762
* DECODING when media stop seamless looping
763
*/
764
class MediaDecoderStateMachine::LoopingDecodingState
765
: public MediaDecoderStateMachine::DecodingState {
766
public:
767
explicit LoopingDecodingState(Master* aPtr)
768
: DecodingState(aPtr), mIsReachingAudioEOS(!mMaster->IsAudioDecoding()) {
769
MOZ_ASSERT(mMaster->mLooping);
770
}
771
772
void Enter() {
773
if (mIsReachingAudioEOS) {
774
SLOG("audio has ended, request the data again.");
775
UpdatePlaybackPositionToZeroIfNeeded();
776
RequestAudioDataFromStartPosition();
777
}
778
DecodingState::Enter();
779
}
780
781
void Exit() override {
782
if (ShouldDiscardLoopedAudioData()) {
783
mMaster->mAudioDataRequest.DisconnectIfExists();
784
DiscardLoopedAudioData();
785
}
786
if (HasDecodedLastAudioFrame()) {
787
AudioQueue().Finish();
788
}
789
mAudioDataRequest.DisconnectIfExists();
790
mAudioSeekRequest.DisconnectIfExists();
791
DecodingState::Exit();
792
}
793
794
State GetState() const override { return DECODER_STATE_LOOPING_DECODING; }
795
796
void HandleAudioDecoded(AudioData* aAudio) override {
797
MediaResult rv = LoopingAudioTimeAdjustment(aAudio);
798
if (NS_WARN_IF(NS_FAILED(rv))) {
799
mMaster->DecodeError(rv);
800
return;
801
}
802
mMaster->mDecodedAudioEndTime =
803
std::max(aAudio->GetEndTime(), mMaster->mDecodedAudioEndTime);
804
SLOG("sample after time-adjustment [%" PRId64 ",%" PRId64 "]",
805
aAudio->mTime.ToMicroseconds(), aAudio->GetEndTime().ToMicroseconds());
806
DecodingState::HandleAudioDecoded(aAudio);
807
}
808
809
void HandleEndOfAudio() override {
810
mIsReachingAudioEOS = true;
811
// The data time in the audio queue is assumed to be increased linearly,
812
// so we need to add the last ending time as the offset to correct the
813
// audio data time in the next round when seamless looping is enabled.
814
mAudioLoopingOffset = mMaster->mDecodedAudioEndTime;
815
816
if (mMaster->mAudioDecodedDuration.isNothing()) {
817
mMaster->mAudioDecodedDuration.emplace(mMaster->mDecodedAudioEndTime);
818
}
819
820
SLOG(
821
"received EOS when seamless looping, starts seeking, "
822
"AudioLoopingOffset=[%" PRId64 "]",
823
mAudioLoopingOffset.ToMicroseconds());
824
RequestAudioDataFromStartPosition();
825
}
826
827
private:
828
void RequestAudioDataFromStartPosition() {
829
Reader()->ResetDecode(TrackInfo::kAudioTrack);
830
Reader()
831
->Seek(SeekTarget(media::TimeUnit::Zero(), SeekTarget::Accurate))
832
->Then(
833
OwnerThread(), __func__,
834
[this]() -> void {
835
mAudioSeekRequest.Complete();
836
SLOG(
837
"seeking completed, start to request first sample, "
838
"queueing audio task - queued=%zu, decoder-queued=%zu",
839
AudioQueue().GetSize(), Reader()->SizeOfAudioQueueInFrames());
840
841
Reader()
842
->RequestAudioData()
843
->Then(
844
OwnerThread(), __func__,
845
[this](RefPtr<AudioData> aAudio) {
846
mIsReachingAudioEOS = false;
847
mAudioDataRequest.Complete();
848
SLOG(
849
"got audio decoded sample "
850
"[%" PRId64 ",%" PRId64 "]",
851
aAudio->mTime.ToMicroseconds(),
852
aAudio->GetEndTime().ToMicroseconds());
853
HandleAudioDecoded(aAudio);
854
},
855
[this](const MediaResult& aError) {
856
mAudioDataRequest.Complete();
857
HandleError(aError);
858
})
859
->Track(mAudioDataRequest);
860
},
861
[this](const SeekRejectValue& aReject) -> void {
862
mAudioSeekRequest.Complete();
863
HandleError(aReject.mError);
864
})
865
->Track(mAudioSeekRequest);
866
}
867
868
void UpdatePlaybackPositionToZeroIfNeeded() {
869
MOZ_ASSERT(mIsReachingAudioEOS);
870
MOZ_ASSERT(mAudioLoopingOffset == media::TimeUnit::Zero());
871
// If we have already reached EOS before starting media sink, the sink
872
// has not started yet and the current position is larger than last decoded
873
// end time, that means we directly seeked to EOS and playback would start
874
// from the start position soon. Therefore, we should reset the position to
875
// 0s so that when media sink starts we can make it start from 0s, not from
876
// EOS position which would result in wrong estimation of decoded audio
877
// duration because decoded data's time which can't be adjusted as offset is
878
// zero would be always less than media sink time.
879
if (!mMaster->mMediaSink->IsStarted() &&
880
mMaster->mCurrentPosition.Ref() > mMaster->mDecodedAudioEndTime) {
881
mMaster->UpdatePlaybackPositionInternal(TimeUnit::Zero());
882
}
883
}
884
885
void HandleError(const MediaResult& aError);
886
887
void EnsureAudioDecodeTaskQueued() override {
888
if (mAudioSeekRequest.Exists() || mAudioDataRequest.Exists()) {
889
return;
890
}
891
DecodingState::EnsureAudioDecodeTaskQueued();
892
}
893
894
MediaResult LoopingAudioTimeAdjustment(AudioData* aAudio) {
895
if (mAudioLoopingOffset != media::TimeUnit::Zero()) {
896
aAudio->mTime += mAudioLoopingOffset;
897
}
898
return aAudio->mTime.IsValid()
899
? MediaResult(NS_OK)
900
: MediaResult(
901
NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
902
"Audio sample overflow during looping time adjustment");
903
}
904
905
bool ShouldDiscardLoopedAudioData() const {
906
if (!mMaster->mMediaSink->IsStarted()) {
907
return false;
908
}
909
/**
910
* If media cancels looping, we should check whether there are audio data
911
* whose time is later than EOS. If so, we should discard them because we
912
* won't have a chance to play them.
913
*
914
* playback last decoded
915
* position EOS data time
916
* ----|---------------|------------|---------> (Increasing timeline)
917
* mCurrent mLooping mMaster's
918
* ClockTime Offset mDecodedAudioEndTime
919
*
920
*/
921
return (mAudioLoopingOffset != media::TimeUnit::Zero() &&
922
mMaster->GetClock() < mAudioLoopingOffset &&
923
mAudioLoopingOffset < mMaster->mDecodedAudioEndTime);
924
}
925
926
void DiscardLoopedAudioData() {
927
if (mAudioLoopingOffset == media::TimeUnit::Zero()) {
928
return;
929
}
930
931
SLOG("Discard frames after the time=%" PRId64,
932
mAudioLoopingOffset.ToMicroseconds());
933
DiscardFramesFromTail(AudioQueue(), [&](int64_t aSampleTime) {
934
return aSampleTime > mAudioLoopingOffset.ToMicroseconds();
935
});
936
}
937
938
bool HasDecodedLastAudioFrame() const {
939
// when we're going to leave looping state and have got EOS before, we
940
// should mark audio queue as ended because we have got all data we need.
941
return mAudioDataRequest.Exists() || mAudioSeekRequest.Exists() ||
942
ShouldDiscardLoopedAudioData();
943
}
944
945
bool mIsReachingAudioEOS;
946
media::TimeUnit mAudioLoopingOffset = media::TimeUnit::Zero();
947
MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mAudioSeekRequest;
948
MozPromiseRequestHolder<AudioDataPromise> mAudioDataRequest;
949
};
950
951
/**
952
* Purpose: seek to a particular new playback position.
953
*
954
* Transition to:
955
* SEEKING if any new seek request.
956
* SHUTDOWN if seek failed.
957
* COMPLETED if the new playback position is the end of the media resource.
958
* NextFrameSeekingState if completing a NextFrameSeekingFromDormantState.
959
* DECODING/LOOPING_DECODING otherwise.
960
*/
961
class MediaDecoderStateMachine::SeekingState
962
: public MediaDecoderStateMachine::StateObject {
963
public:
964
explicit SeekingState(Master* aPtr)
965
: StateObject(aPtr), mVisibility(static_cast<EventVisibility>(0)) {}
966
967
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
968
EventVisibility aVisibility) {
969
mSeekJob = std::move(aSeekJob);
970
mVisibility = aVisibility;
971
972
// Suppressed visibility comes from two cases: (1) leaving dormant state,
973
// and (2) resuming suspended video decoder. We want both cases to be
974
// transparent to the user. So we only notify the change when the seek
975
// request is from the user.
976
if (mVisibility == EventVisibility::Observable) {
977
// Don't stop playback for a video-only seek since we want to keep playing
978
// audio and we don't need to stop playback while leaving dormant for the
979
// playback should has been stopped.
980
mMaster->StopPlayback();
981
mMaster->UpdatePlaybackPositionInternal(mSeekJob.mTarget->GetTime());
982
mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::SeekStarted);
983
mMaster->mOnNextFrameStatus.Notify(
984
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
985
}
986
987
RefPtr<MediaDecoder::SeekPromise> p = mSeekJob.mPromise.Ensure(__func__);
988
989
DoSeek();
990
991
return p;
992
}
993
994
virtual void Exit() override = 0;
995
996
State GetState() const override = 0;
997
998
void HandleAudioDecoded(AudioData* aAudio) override = 0;
999
void HandleVideoDecoded(VideoData* aVideo,
1000
TimeStamp aDecodeStart) override = 0;
1001
void HandleAudioWaited(MediaData::Type aType) override = 0;
1002
void HandleVideoWaited(MediaData::Type aType) override = 0;
1003
1004
void HandleVideoSuspendTimeout() override {
1005
// Do nothing since we want a valid video frame to show when seek is done.
1006
}
1007
1008
void HandleResumeVideoDecoding(const TimeUnit&) override {
1009
// Do nothing. We will resume video decoding in the decoding state.
1010
}
1011
1012
// We specially handle next frame seeks by ignoring them if we're already
1013
// seeking.
1014
RefPtr<MediaDecoder::SeekPromise> HandleSeek(SeekTarget aTarget) override {
1015
if (aTarget.IsNextFrame()) {
1016
// We ignore next frame seeks if we already have a seek pending
1017
SLOG("Already SEEKING, ignoring seekToNextFrame");
1018
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1019
return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true,
1020
__func__);
1021
}
1022
1023
return StateObject::HandleSeek(aTarget);
1024
}
1025
1026
protected:
1027
SeekJob mSeekJob;
1028
EventVisibility mVisibility;
1029
1030
virtual void DoSeek() = 0;
1031
// Transition to the next state (defined by the subclass) when seek is
1032
// completed.
1033
virtual void GoToNextState() { SetDecodingState(); }
1034
void SeekCompleted();
1035
virtual TimeUnit CalculateNewCurrentTime() const = 0;
1036
};
1037
1038
class MediaDecoderStateMachine::AccurateSeekingState
1039
: public MediaDecoderStateMachine::SeekingState {
1040
public:
1041
explicit AccurateSeekingState(Master* aPtr) : SeekingState(aPtr) {}
1042
1043
State GetState() const override { return DECODER_STATE_SEEKING_ACCURATE; }
1044
1045
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
1046
EventVisibility aVisibility) {
1047
MOZ_ASSERT(aSeekJob.mTarget->IsAccurate() || aSeekJob.mTarget->IsFast());
1048
mCurrentTimeBeforeSeek = mMaster->GetMediaTime();
1049
return SeekingState::Enter(std::move(aSeekJob), aVisibility);
1050
}
1051
1052
void Exit() override {
1053
// Disconnect MediaDecoder.
1054
mSeekJob.RejectIfExists(__func__);
1055
1056
// Disconnect ReaderProxy.
1057
mSeekRequest.DisconnectIfExists();
1058
1059
mWaitRequest.DisconnectIfExists();
1060
}
1061
1062
void HandleAudioDecoded(AudioData* aAudio) override {
1063
MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1064
"Seek shouldn't be finished");
1065
MOZ_ASSERT(aAudio);
1066
1067
AdjustFastSeekIfNeeded(aAudio);
1068
1069
if (mSeekJob.mTarget->IsFast()) {
1070
// Non-precise seek; we can stop the seek at the first sample.
1071
mMaster->PushAudio(aAudio);
1072
mDoneAudioSeeking = true;
1073
} else {
1074
nsresult rv = DropAudioUpToSeekTarget(aAudio);
1075
if (NS_FAILED(rv)) {
1076
mMaster->DecodeError(rv);
1077
return;
1078
}
1079
}
1080
1081
if (!mDoneAudioSeeking) {
1082
RequestAudioData();
1083
return;
1084
}
1085
MaybeFinishSeek();
1086
}
1087
1088
void HandleVideoDecoded(VideoData* aVideo, TimeStamp aDecodeStart) override {
1089
MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1090
"Seek shouldn't be finished");
1091
MOZ_ASSERT(aVideo);
1092
1093
AdjustFastSeekIfNeeded(aVideo);
1094
1095
if (mSeekJob.mTarget->IsFast()) {
1096
// Non-precise seek. We can stop the seek at the first sample.
1097
mMaster->PushVideo(aVideo);
1098
mDoneVideoSeeking = true;
1099
} else {
1100
nsresult rv = DropVideoUpToSeekTarget(aVideo);
1101
if (NS_FAILED(rv)) {
1102
mMaster->DecodeError(rv);
1103
return;
1104
}
1105
}
1106
1107
if (!mDoneVideoSeeking) {
1108
RequestVideoData();
1109
return;
1110
}
1111
MaybeFinishSeek();
1112
}
1113
1114
void HandleWaitingForAudio() override {
1115
MOZ_ASSERT(!mDoneAudioSeeking);
1116
mMaster->WaitForData(MediaData::Type::AUDIO_DATA);
1117
}
1118
1119
void HandleAudioCanceled() override {
1120
MOZ_ASSERT(!mDoneAudioSeeking);
1121
RequestAudioData();
1122
}
1123
1124
void HandleEndOfAudio() override {
1125
HandleEndOfAudioInternal();
1126
MaybeFinishSeek();
1127
}
1128
1129
void HandleWaitingForVideo() override {
1130
MOZ_ASSERT(!mDoneVideoSeeking);
1131
mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
1132
}
1133
1134
void HandleVideoCanceled() override {
1135
MOZ_ASSERT(!mDoneVideoSeeking);
1136
RequestVideoData();
1137
}
1138
1139
void HandleEndOfVideo() override {
1140
HandleEndOfVideoInternal();
1141
MaybeFinishSeek();
1142
}
1143
1144
void HandleAudioWaited(MediaData::Type aType) override {
1145
MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1146
"Seek shouldn't be finished");
1147
1148
RequestAudioData();
1149
}
1150
1151
void HandleVideoWaited(MediaData::Type aType) override {
1152
MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1153
"Seek shouldn't be finished");
1154
1155
RequestVideoData();
1156
}
1157
1158
void DoSeek() override {
1159
mDoneAudioSeeking = !Info().HasAudio();
1160
mDoneVideoSeeking = !Info().HasVideo();
1161
1162
mMaster->ResetDecode();
1163
mMaster->StopMediaSink();
1164
1165
DemuxerSeek();
1166
}
1167
1168
TimeUnit CalculateNewCurrentTime() const override {
1169
const auto seekTime = mSeekJob.mTarget->GetTime();
1170
1171
// For the accurate seek, we always set the newCurrentTime = seekTime so
1172
// that the updated HTMLMediaElement.currentTime will always be the seek
1173
// target; we rely on the MediaSink to handles the gap between the
1174
// newCurrentTime and the real decoded samples' start time.
1175
if (mSeekJob.mTarget->IsAccurate()) {
1176
return seekTime;
1177
}
1178
1179
// For the fast seek, we update the newCurrentTime with the decoded audio
1180
// and video samples, set it to be the one which is closet to the seekTime.
1181
if (mSeekJob.mTarget->IsFast()) {
1182
RefPtr<AudioData> audio = AudioQueue().PeekFront();
1183
RefPtr<VideoData> video = VideoQueue().PeekFront();
1184
1185
// A situation that both audio and video approaches the end.
1186
if (!audio && !video) {
1187
return seekTime;
1188
}
1189
1190
const int64_t audioStart =
1191
audio ? audio->mTime.ToMicroseconds() : INT64_MAX;
1192
const int64_t videoStart =
1193
video ? video->mTime.ToMicroseconds() : INT64_MAX;
1194
const int64_t audioGap = std::abs(audioStart - seekTime.ToMicroseconds());
1195
const int64_t videoGap = std::abs(videoStart - seekTime.ToMicroseconds());
1196
return TimeUnit::FromMicroseconds(audioGap <= videoGap ? audioStart
1197
: videoStart);
1198
}
1199
1200
MOZ_ASSERT(false, "AccurateSeekTask doesn't handle other seek types.");
1201
return TimeUnit::Zero();
1202
}
1203
1204
protected:
1205
void DemuxerSeek() {
1206
// Request the demuxer to perform seek.
1207
Reader()
1208
->Seek(mSeekJob.mTarget.ref())
1209
->Then(
1210
OwnerThread(), __func__,
1211
[this](const media::TimeUnit& aUnit) { OnSeekResolved(aUnit); },
1212
[this](const SeekRejectValue& aReject) { OnSeekRejected(aReject); })
1213
->Track(mSeekRequest);
1214
}
1215
1216
void OnSeekResolved(media::TimeUnit) {
1217
mSeekRequest.Complete();
1218
1219
// We must decode the first samples of active streams, so we can determine
1220
// the new stream time. So dispatch tasks to do that.
1221
if (!mDoneVideoSeeking) {
1222
RequestVideoData();
1223
}
1224
if (!mDoneAudioSeeking) {
1225
RequestAudioData();
1226
}
1227
}
1228
1229
void OnSeekRejected(const SeekRejectValue& aReject) {
1230
mSeekRequest.Complete();
1231
1232
if (aReject.mError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
1233
SLOG("OnSeekRejected reason=WAITING_FOR_DATA type=%s",
1234
MediaData::TypeToStr(aReject.mType));
1235
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::AUDIO_DATA,
1236
!mMaster->IsRequestingAudioData());
1237
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::VIDEO_DATA,
1238
!mMaster->IsRequestingVideoData());
1239
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::AUDIO_DATA,
1240
!mMaster->IsWaitingAudioData());
1241
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::VIDEO_DATA,
1242
!mMaster->IsWaitingVideoData());
1243
1244
// Fire 'waiting' to notify the player that we are waiting for data.
1245
mMaster->mOnNextFrameStatus.Notify(
1246
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
1247
1248
Reader()
1249
->WaitForData(aReject.mType)
1250
->Then(
1251
OwnerThread(), __func__,
1252
[this](MediaData::Type aType) {
1253
SLOG("OnSeekRejected wait promise resolved");
1254
mWaitRequest.Complete();
1255
DemuxerSeek();
1256
},
1257
[this](const WaitForDataRejectValue& aRejection) {
1258
SLOG("OnSeekRejected wait promise rejected");
1259
mWaitRequest.Complete();
1260
mMaster->DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
1261
})
1262
->Track(mWaitRequest);
1263
return;
1264
}
1265
1266
if (aReject.mError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
1267
if (!mDoneAudioSeeking) {
1268
HandleEndOfAudioInternal();
1269
}
1270
if (!mDoneVideoSeeking) {
1271
HandleEndOfVideoInternal();
1272
}
1273
MaybeFinishSeek();
1274
return;
1275
}
1276
1277
MOZ_ASSERT(NS_FAILED(aReject.mError),
1278
"Cancels should also disconnect mSeekRequest");
1279
mMaster->DecodeError(aReject.mError);
1280
}
1281
1282
void RequestAudioData() {
1283
MOZ_ASSERT(!mDoneAudioSeeking);
1284
mMaster->RequestAudioData();
1285
}
1286
1287
virtual void RequestVideoData() {
1288
MOZ_ASSERT(!mDoneVideoSeeking);
1289
mMaster->RequestVideoData(media::TimeUnit());
1290
}
1291
1292
void AdjustFastSeekIfNeeded(MediaData* aSample) {
1293
if (mSeekJob.mTarget->IsFast() &&
1294
mSeekJob.mTarget->GetTime() > mCurrentTimeBeforeSeek &&
1295
aSample->mTime < mCurrentTimeBeforeSeek) {
1296
// We are doing a fastSeek, but we ended up *before* the previous
1297
// playback position. This is surprising UX, so switch to an accurate
1298
// seek and decode to the seek target. This is not conformant to the
1299
// spec, fastSeek should always be fast, but until we get the time to
1300
// change all Readers to seek to the keyframe after the currentTime
1301
// in this case, we'll just decode forward. Bug 1026330.
1302
mSeekJob.mTarget->SetType(SeekTarget::Accurate);
1303
}
1304
}
1305
1306
nsresult DropAudioUpToSeekTarget(AudioData* aAudio) {
1307
MOZ_ASSERT(aAudio && mSeekJob.mTarget->IsAccurate());
1308
1309
if (mSeekJob.mTarget->GetTime() >= aAudio->GetEndTime()) {
1310
// Our seek target lies after the frames in this AudioData. Don't
1311
// push it onto the audio queue, and keep decoding forwards.
1312
return NS_OK;
1313
}
1314
1315
if (aAudio->mTime > mSeekJob.mTarget->GetTime()) {
1316
// The seek target doesn't lie in the audio block just after the last
1317
// audio frames we've seen which were before the seek target. This
1318
// could have been the first audio data we've seen after seek, i.e. the
1319
// seek terminated after the seek target in the audio stream. Just
1320
// abort the audio decode-to-target, the state machine will play
1321
// silence to cover the gap. Typically this happens in poorly muxed
1322
// files.
1323
SLOGW("Audio not synced after seek, maybe a poorly muxed file?");
1324
mMaster->PushAudio(aAudio);
1325
mDoneAudioSeeking = true;
1326
return NS_OK;
1327
}
1328
1329
bool ok = aAudio->SetTrimWindow(
1330
{mSeekJob.mTarget->GetTime(), aAudio->GetEndTime()});
1331
if (!ok) {
1332
return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
1333
}
1334
1335
MOZ_ASSERT(AudioQueue().GetSize() == 0,
1336
"Should be the 1st sample after seeking");
1337
mMaster->PushAudio(aAudio);
1338
mDoneAudioSeeking = true;
1339
1340
return NS_OK;
1341
}
1342
1343
nsresult DropVideoUpToSeekTarget(VideoData* aVideo) {
1344
MOZ_ASSERT(aVideo);
1345
SLOG("DropVideoUpToSeekTarget() frame [%" PRId64 ", %" PRId64 "]",
1346
aVideo->mTime.ToMicroseconds(), aVideo->GetEndTime().ToMicroseconds());
1347
const auto target = GetSeekTarget();
1348
1349
// If the frame end time is less than the seek target, we won't want
1350
// to display this frame after the seek, so discard it.
1351
if (target >= aVideo->GetEndTime()) {
1352
SLOG("DropVideoUpToSeekTarget() pop video frame [%" PRId64 ", %" PRId64
1353
"] target=%" PRId64,
1354
aVideo->mTime.ToMicroseconds(),
1355
aVideo->GetEndTime().ToMicroseconds(), target.ToMicroseconds());
1356
mFirstVideoFrameAfterSeek = aVideo;
1357
} else {
1358
if (target >= aVideo->mTime && aVideo->GetEndTime() >= target) {
1359
// The seek target lies inside this frame's time slice. Adjust the
1360
// frame's start time to match the seek target.
1361
aVideo->UpdateTimestamp(target);
1362
}
1363
mFirstVideoFrameAfterSeek = nullptr;
1364
1365
SLOG("DropVideoUpToSeekTarget() found video frame [%" PRId64 ", %" PRId64
1366
"] containing target=%" PRId64,
1367
aVideo->mTime.ToMicroseconds(),
1368
aVideo->GetEndTime().ToMicroseconds(), target.ToMicroseconds());
1369
1370
MOZ_ASSERT(VideoQueue().GetSize() == 0,
1371
"Should be the 1st sample after seeking");
1372
mMaster->PushVideo(aVideo);
1373
mDoneVideoSeeking = true;
1374
}
1375
1376
return NS_OK;
1377
}
1378
1379
void HandleEndOfAudioInternal() {
1380
MOZ_ASSERT(!mDoneAudioSeeking);
1381
AudioQueue().Finish();
1382
mDoneAudioSeeking = true;
1383
}
1384
1385
void HandleEndOfVideoInternal() {
1386
MOZ_ASSERT(!mDoneVideoSeeking);
1387
if (mFirstVideoFrameAfterSeek) {
1388
// Hit the end of stream. Move mFirstVideoFrameAfterSeek into
1389
// mSeekedVideoData so we have something to display after seeking.
1390
mMaster->PushVideo(mFirstVideoFrameAfterSeek);
1391
}
1392
VideoQueue().Finish();
1393
mDoneVideoSeeking = true;
1394
}
1395
1396
void MaybeFinishSeek() {
1397
if (mDoneAudioSeeking && mDoneVideoSeeking) {
1398
SeekCompleted();
1399
}
1400
}
1401
1402
/*
1403
* Track the current seek promise made by the reader.
1404
*/
1405
MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mSeekRequest;
1406
1407
/*
1408
* Internal state.
1409
*/
1410
media::TimeUnit mCurrentTimeBeforeSeek;
1411
bool mDoneAudioSeeking = false;
1412
bool mDoneVideoSeeking = false;
1413
MozPromiseRequestHolder<WaitForDataPromise> mWaitRequest;
1414
1415
// This temporarily stores the first frame we decode after we seek.
1416
// This is so that if we hit end of stream while we're decoding to reach
1417
// the seek target, we will still have a frame that we can display as the
1418
// last frame in the media.
1419
RefPtr<VideoData> mFirstVideoFrameAfterSeek;
1420
1421
private:
1422
virtual media::TimeUnit GetSeekTarget() const {
1423
return mSeekJob.mTarget->GetTime();
1424
}
1425
};
1426
1427
/*
1428
* Remove samples from the queue until aCompare() returns false.
1429
* aCompare A function object with the signature bool(int64_t) which returns
1430
* true for samples that should be removed.
1431
*/
1432
template <typename Type, typename Function>
1433
static void DiscardFrames(MediaQueue<Type>& aQueue, const Function& aCompare) {
1434
while (aQueue.GetSize() > 0) {
1435
if (aCompare(aQueue.PeekFront()->mTime.ToMicroseconds())) {
1436
RefPtr<Type> releaseMe = aQueue.PopFront();
1437
continue;
1438
}
1439
break;
1440
}
1441
}
1442
1443
class MediaDecoderStateMachine::NextFrameSeekingState
1444
: public MediaDecoderStateMachine::SeekingState {
1445
public:
1446
explicit NextFrameSeekingState(Master* aPtr) : SeekingState(aPtr) {}
1447
1448
State GetState() const override {
1449
return DECODER_STATE_SEEKING_NEXTFRAMESEEKING;
1450
}
1451
1452
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
1453
EventVisibility aVisibility) {
1454
MOZ_ASSERT(aSeekJob.mTarget->IsNextFrame());
1455
mCurrentTime = mMaster->GetMediaTime();
1456
mDuration = mMaster->Duration();
1457
return SeekingState::Enter(std::move(aSeekJob), aVisibility);
1458
}
1459
1460
void Exit() override {
1461
// Disconnect my async seek operation.
1462
if (mAsyncSeekTask) {
1463
mAsyncSeekTask->Cancel();
1464
}
1465
1466
// Disconnect MediaDecoder.
1467
mSeekJob.RejectIfExists(__func__);
1468
}
1469
1470
void HandleAudioDecoded(AudioData* aAudio) override {
1471
mMaster->PushAudio(aAudio);
1472
}
1473
1474
void HandleVideoDecoded(VideoData* aVideo, TimeStamp aDecodeStart) override {
1475
MOZ_ASSERT(aVideo);
1476
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1477
MOZ_ASSERT(NeedMoreVideo());
1478
1479
if (aVideo->mTime > mCurrentTime) {
1480
mMaster->PushVideo(aVideo);
1481
FinishSeek();
1482
} else {
1483
RequestVideoData();
1484
}
1485
}
1486
1487
void HandleWaitingForAudio() override {
1488
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1489
// We don't care about audio decode errors in this state which will be
1490
// handled by other states after seeking.
1491
}
1492
1493
void HandleAudioCanceled() override {
1494
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1495
// We don't care about audio decode errors in this state which will be
1496
// handled by other states after seeking.
1497
}
1498
1499
void HandleEndOfAudio() override {
1500
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1501
// We don't care about audio decode errors in this state which will be
1502
// handled by other states after seeking.
1503
}
1504
1505
void HandleWaitingForVideo() override {
1506
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1507
MOZ_ASSERT(NeedMoreVideo());
1508
mMaster->WaitForData(MediaData::Type::VIDEO_DATA);
1509
}
1510
1511
void HandleVideoCanceled() override {
1512
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1513
MOZ_ASSERT(NeedMoreVideo());
1514
RequestVideoData();
1515
}
1516
1517
void HandleEndOfVideo() override {
1518
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1519
MOZ_ASSERT(NeedMoreVideo());
1520
VideoQueue().Finish();
1521
FinishSeek();
1522
}
1523
1524
void HandleAudioWaited(MediaData::Type aType) override {
1525
// We don't care about audio in this state.
1526
}
1527
1528
void HandleVideoWaited(MediaData::Type aType) override {
1529
MOZ_ASSERT(!mSeekJob.mPromise.IsEmpty(), "Seek shouldn't be finished");
1530
MOZ_ASSERT(NeedMoreVideo());
1531
RequestVideoData();
1532
}
1533
1534
TimeUnit CalculateNewCurrentTime() const override {
1535
// The HTMLMediaElement.currentTime should be updated to the seek target
1536
// which has been updated to the next frame's time.
1537
return mSeekJob.mTarget->GetTime();
1538
}
1539
1540
void DoSeek() override {
1541
auto currentTime = mCurrentTime;
1542
DiscardFrames(VideoQueue(), [currentTime](int64_t aSampleTime) {
1543
return aSampleTime <= currentTime.ToMicroseconds();
1544
});
1545
1546
// If there is a pending video request, finish the seeking if we don't need
1547
// more data, or wait for HandleVideoDecoded() to finish seeking.
1548
if (mMaster->IsRequestingVideoData()) {
1549
if (!NeedMoreVideo()) {
1550
FinishSeek();
1551
}
1552
return;
1553
}
1554
1555
// Otherwise, we need to do the seek operation asynchronously for a special
1556
// case (bug504613.ogv) which has no data at all, the 1st seekToNextFrame()
1557
// operation reaches the end of the media. If we did the seek operation
1558
// synchronously, we immediately resolve the SeekPromise in mSeekJob and
1559
// then switch to the CompletedState which dispatches an "ended" event.
1560
// However, the ThenValue of the SeekPromise has not yet been set, so the
1561
// promise resolving is postponed and then the JS developer receives the
1562
// "ended" event before the seek promise is resolved.
1563
// An asynchronous seek operation helps to solve this issue since while the
1564
// seek is actually performed, the ThenValue of SeekPromise has already
1565
// been set so that it won't be postponed.
1566
RefPtr<Runnable> r = mAsyncSeekTask = new AysncNextFrameSeekTask(this);
1567
nsresult rv = OwnerThread()->Dispatch(r.forget());
1568
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
1569
Unused << rv;
1570
}
1571
1572
private:
1573
void DoSeekInternal() {
1574
// We don't need to discard frames to the mCurrentTime here because we have
1575
// done it at DoSeek() and any video data received in between either
1576
// finishes the seek operation or be discarded, see HandleVideoDecoded().
1577
1578
if (!NeedMoreVideo()) {
1579
FinishSeek();
1580
} else if (!mMaster->IsRequestingVideoData() &&
1581
!mMaster->IsWaitingVideoData()) {
1582
RequestVideoData();
1583
}
1584
}
1585
1586
class AysncNextFrameSeekTask : public Runnable {
1587
public:
1588
explicit AysncNextFrameSeekTask(NextFrameSeekingState* aStateObject)
1589
: Runnable(
1590
"MediaDecoderStateMachine::NextFrameSeekingState::"
1591
"AysncNextFrameSeekTask"),
1592
mStateObj(aStateObject) {}
1593
1594
void Cancel() { mStateObj = nullptr; }
1595
1596
NS_IMETHOD Run() override {
1597
if (mStateObj) {
1598
mStateObj->DoSeekInternal();
1599
}
1600
return NS_OK;
1601
}
1602
1603
private:
1604
NextFrameSeekingState* mStateObj;
1605
};
1606
1607
void RequestVideoData() { mMaster->RequestVideoData(media::TimeUnit()); }
1608
1609
bool NeedMoreVideo() const {
1610
// Need to request video when we have none and video queue is not finished.
1611
return VideoQueue().GetSize() == 0 && !VideoQueue().IsFinished();
1612
}
1613
1614
// Update the seek target's time before resolving this seek task, the updated
1615
// time will be used in the MDSM::SeekCompleted() to update the MDSM's
1616
// position.
1617
void UpdateSeekTargetTime() {
1618
RefPtr<VideoData> data = VideoQueue().PeekFront();
1619
if (data) {
1620
mSeekJob.mTarget->SetTime(data->mTime);
1621
} else {
1622
MOZ_ASSERT(VideoQueue().AtEndOfStream());
1623
mSeekJob.mTarget->SetTime(mDuration);
1624
}
1625
}
1626
1627
void FinishSeek() {
1628
MOZ_ASSERT(!NeedMoreVideo());
1629
UpdateSeekTargetTime();
1630
auto time = mSeekJob.mTarget->GetTime().ToMicroseconds();
1631
DiscardFrames(AudioQueue(),
1632
[time](int64_t aSampleTime) { return aSampleTime < time; });
1633
SeekCompleted();
1634
}
1635
1636
/*
1637
* Internal state.
1638
*/
1639
TimeUnit mCurrentTime;
1640
TimeUnit mDuration;
1641
RefPtr<AysncNextFrameSeekTask> mAsyncSeekTask;
1642
};
1643
1644
class MediaDecoderStateMachine::NextFrameSeekingFromDormantState
1645
: public MediaDecoderStateMachine::AccurateSeekingState {
1646
public:
1647
explicit NextFrameSeekingFromDormantState(Master* aPtr)
1648
: AccurateSeekingState(aPtr) {}
1649
1650
State GetState() const override { return DECODER_STATE_SEEKING_FROMDORMANT; }
1651
1652
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aCurrentSeekJob,
1653
SeekJob&& aFutureSeekJob) {
1654
mFutureSeekJob = std::move(aFutureSeekJob);
1655
1656
AccurateSeekingState::Enter(std::move(aCurrentSeekJob),
1657
EventVisibility::Suppressed);
1658
1659
// Once seekToNextFrame() is called, we assume the user is likely to keep
1660
// calling seekToNextFrame() repeatedly, and so, we should prevent the MDSM
1661
// from getting into Dormant state.
1662
mMaster->mMinimizePreroll = false;
1663
1664
return mFutureSeekJob.mPromise.Ensure(__func__);
1665
}
1666
1667
void Exit() override {
1668
mFutureSeekJob.RejectIfExists(__func__);
1669
AccurateSeekingState::Exit();
1670
}
1671
1672
private:
1673
SeekJob mFutureSeekJob;
1674
1675
// We don't want to transition to DecodingState once this seek completes,
1676
// instead, we transition to NextFrameSeekingState.
1677
void GoToNextState() override {
1678
SetState<NextFrameSeekingState>(std::move(mFutureSeekJob),
1679
EventVisibility::Observable);
1680
}
1681
};
1682
1683
class MediaDecoderStateMachine::VideoOnlySeekingState
1684
: public MediaDecoderStateMachine::AccurateSeekingState {
1685
public:
1686
explicit VideoOnlySeekingState(Master* aPtr) : AccurateSeekingState(aPtr) {}
1687
1688
State GetState() const override { return DECODER_STATE_SEEKING_VIDEOONLY; }
1689
1690
RefPtr<MediaDecoder::SeekPromise> Enter(SeekJob&& aSeekJob,
1691
EventVisibility aVisibility) {
1692
MOZ_ASSERT(aSeekJob.mTarget->IsVideoOnly());
1693
MOZ_ASSERT(aVisibility == EventVisibility::Suppressed);
1694
1695
RefPtr<MediaDecoder::SeekPromise> p =
1696
AccurateSeekingState::Enter(std::move(aSeekJob), aVisibility);
1697
1698
// Dispatch a mozvideoonlyseekbegin event to indicate UI for corresponding
1699
// changes.
1700
mMaster->mOnPlaybackEvent.Notify(MediaPlaybackEvent::VideoOnlySeekBegin);
1701
1702
return p.forget();
1703
}
1704
1705
void Exit() override {
1706
// We are completing or discarding this video-only seek operation now,
1707
// dispatch an event so that the UI can change in response to the end
1708
// of video-only seek.
1709
mMaster->mOnPlaybackEvent.Notify(
1710
MediaPlaybackEvent::VideoOnlySeekCompleted);
1711
1712
AccurateSeekingState::Exit();
1713
}
1714
1715
void HandleAudioDecoded(AudioData* aAudio) override {
1716
MOZ_ASSERT(mDoneAudioSeeking && !mDoneVideoSeeking,
1717
"Seek shouldn't be finished");
1718
MOZ_ASSERT(aAudio);
1719
1720
// Video-only seek doesn't reset audio decoder. There might be pending audio
1721
// requests when AccurateSeekTask::Seek() begins. We will just store the
1722
// data without checking |mDiscontinuity| or calling
1723
// DropAudioUpToSeekTarget().
1724
mMaster->PushAudio(aAudio);
1725
}
1726
1727
void HandleWaitingForAudio() override {}
1728
1729
void HandleAudioCanceled() override {}
1730
1731
void HandleEndOfAudio() override {}
1732
1733
void HandleAudioWaited(MediaData::Type aType) override {
1734
MOZ_ASSERT(!mDoneAudioSeeking || !mDoneVideoSeeking,
1735
"Seek shouldn't be finished");
1736
1737
// Ignore pending requests from video-only seek.
1738
}
1739
1740
void DoSeek() override {
1741
// TODO: keep decoding audio.
1742
mDoneAudioSeeking = true;
1743
mDoneVideoSeeking = !Info().HasVideo();
1744
1745
mMaster->ResetDecode(TrackInfo::kVideoTrack);
1746
1747
DemuxerSeek();
1748
}
1749
1750
protected:
1751
// Allow skip-to-next-key-frame to kick in if we fall behind the current
1752
// playback position so decoding has a better chance to catch up.
1753
void RequestVideoData() override {
1754
MOZ_ASSERT(!mDoneVideoSeeking);
1755
1756
const auto& clock = mMaster->mMediaSink->IsStarted()
1757
? mMaster->GetClock()
1758
: mMaster->GetMediaTime();
1759
const auto& nextKeyFrameTime = GetNextKeyFrameTime();
1760
1761
auto threshold = clock;
1762
1763
if (nextKeyFrameTime.IsValid() &&
1764
clock >= (nextKeyFrameTime - sSkipToNextKeyFrameThreshold)) {
1765
threshold = nextKeyFrameTime;
1766
}
1767
1768
mMaster->RequestVideoData(threshold);
1769
}
1770
1771
private:
1772
// Trigger skip to next key frame if the current playback position is very
1773
// close the next key frame's time.
1774
static constexpr TimeUnit sSkipToNextKeyFrameThreshold =
1775
TimeUnit::FromMicroseconds(5000);
1776
1777
// If the media is playing, drop video until catch up playback position.
1778
media::TimeUnit GetSeekTarget() const override {
1779
return mMaster->mMediaSink->IsStarted() ? mMaster->GetClock()
1780
: mSeekJob.mTarget->GetTime();
1781
}
1782
1783
media::TimeUnit GetNextKeyFrameTime() const {
1784
// We only call this method in RequestVideoData() and we only request video
1785
// data if we haven't done video seeking.
1786
MOZ_DIAGNOSTIC_ASSERT(!mDoneVideoSeeking);
1787
MOZ_DIAGNOSTIC_ASSERT(mMaster->VideoQueue().GetSize() == 0);
1788
1789
if (mFirstVideoFrameAfterSeek) {
1790
return mFirstVideoFrameAfterSeek->NextKeyFrameTime();
1791
}
1792
1793
return TimeUnit::Invalid();
1794
}
1795
};
1796
1797
constexpr TimeUnit MediaDecoderStateMachine::VideoOnlySeekingState::
1798
sSkipToNextKeyFrameThreshold;
1799
1800
RefPtr<MediaDecoder::SeekPromise>
1801
MediaDecoderStateMachine::DormantState::HandleSeek(SeekTarget aTarget) {
1802
if (aTarget.IsNextFrame()) {
1803
// NextFrameSeekingState doesn't reset the decoder unlike
1804
// AccurateSeekingState. So we first must come out of dormant by seeking to
1805
// mPendingSeek and continue later with the NextFrameSeek
1806