Bug 1235301 part 2 - implement NextFrameSeekTask; r=jwwang
MozReview-Commit-ID: 3ucCLzT6w27
new file mode 100644
--- /dev/null
+++ b/dom/media/NextFrameSeekTask.cpp
@@ -0,0 +1,491 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NextFrameSeekTask.h"
+#include "MediaDecoderReaderWrapper.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Assertions.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+extern LazyLogModule gMediaDecoderLog;
+extern LazyLogModule gMediaSampleLog;
+
+// avoid redefined macro in unified build
+#undef LOG
+#undef DECODER_LOG
+#undef VERBOSE_LOG
+
+#define LOG(m, l, x, ...) \
+ MOZ_LOG(m, l, ("[NextFrameSeekTask] Decoder=%p " x, mDecoderID, ##__VA_ARGS__))
+#define DECODER_LOG(x, ...) \
+ LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
+#define VERBOSE_LOG(x, ...) \
+ LOG(gMediaDecoderLog, LogLevel::Verbose, x, ##__VA_ARGS__)
+#define SAMPLE_LOG(x, ...) \
+ LOG(gMediaSampleLog, LogLevel::Debug, x, ##__VA_ARGS__)
+
+// Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
+// when __VA_ARGS__ expands to nothing. This is a workaround for it.
+#define DECODER_WARN_HELPER(a, b) NS_WARNING b
+#define DECODER_WARN(x, ...) \
+ DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get()))
+
+namespace media {
+
+NextFrameSeekTask::NextFrameSeekTask(const void* aDecoderID,
+ AbstractThread* aThread,
+ MediaDecoderReaderWrapper* aReader,
+ SeekJob&& aSeekJob,
+ const MediaInfo& aInfo,
+ const media::TimeUnit& aDuration,
+ int64_t aCurrentMediaTime,
+ MediaQueue<MediaData>& aAudioQueue,
+ MediaQueue<MediaData>& aVideoQueue)
+ : SeekTask(aDecoderID, aThread, aReader, Move(aSeekJob))
+ , mAudioQueue(aAudioQueue)
+ , mVideoQueue(aVideoQueue)
+ , mCurrentTimeBeforeSeek(aCurrentMediaTime)
+ , mHasAudio(aInfo.HasAudio())
+ , mHasVideo(aInfo.HasVideo())
+ , mDuration(aDuration)
+{
+ AssertOwnerThread();
+ MOZ_ASSERT(HasVideo());
+
+ // Configure MediaDecoderReaderWrapper.
+ SetMediaDecoderReaderWrapperCallback();
+}
+
+NextFrameSeekTask::~NextFrameSeekTask()
+{
+ AssertOwnerThread();
+ MOZ_ASSERT(mIsDiscarded);
+}
+
+bool
+NextFrameSeekTask::HasAudio() const
+{
+ AssertOwnerThread();
+ return mHasAudio;
+}
+
+bool
+NextFrameSeekTask::HasVideo() const
+{
+ AssertOwnerThread();
+ return mHasVideo;
+}
+
+void
+NextFrameSeekTask::Discard()
+{
+ AssertOwnerThread();
+
+ // Disconnect MediaDecoder.
+ mSeekJob.RejectIfExists(__func__);
+
+ // Disconnect MDSM.
+ RejectIfExist(__func__);
+
+ // Disconnect MediaDecoderReader.
+ CancelMediaDecoderReaderWrapperCallback();
+
+ mIsDiscarded = true;
+}
+
+bool
+NextFrameSeekTask::NeedToResetMDSM() const
+{
+ AssertOwnerThread();
+ return false;
+}
+
+static int64_t
+FindNextFrame(MediaQueue<MediaData>& aQueue, int64_t aTime)
+{
+ AutoTArray<RefPtr<MediaData>, 16> frames;
+ aQueue.GetFirstElements(aQueue.GetSize(), &frames);
+ for (auto&& frame : frames) {
+ if (frame->mTime > aTime) {
+ return frame->mTime;
+ }
+ }
+ return -1;
+}
+
+static void
+DropFramesUntil(MediaQueue<MediaData>& aQueue, int64_t aTime) {
+ while (aQueue.GetSize() > 0) {
+ if (aQueue.PeekFront()->mTime < aTime) {
+ RefPtr<MediaData> releaseMe = aQueue.PopFront();
+ continue;
+ }
+ break;
+ }
+}
+
+static void
+DropAllFrames(MediaQueue<MediaData>& aQueue) {
+ while(aQueue.GetSize() > 0) {
+ RefPtr<MediaData> releaseMe = aQueue.PopFront();
+ }
+}
+
+static void
+DropAllMediaDataBeforeCurrentPosition(MediaQueue<MediaData>& aAudioQueue,
+ MediaQueue<MediaData>& aVideoQueue,
+ int64_t const aCurrentTimeBeforeSeek)
+{
+ // Drop all audio/video data before GetMediaTime();
+ int64_t newPos = FindNextFrame(aVideoQueue, aCurrentTimeBeforeSeek);
+ if (newPos < 0) {
+ // In this case, we cannot find the next frame in the video queue, so
+ // the NextFrameSeekTask needs to decode video data.
+ DropAllFrames(aVideoQueue);
+ if (aVideoQueue.IsFinished()) {
+ DropAllFrames(aAudioQueue);
+ }
+ } else {
+ DropFramesUntil(aVideoQueue, newPos);
+ DropFramesUntil(aAudioQueue, newPos);
+ // So now, the 1st data in the video queue should be the target of the
+ // NextFrameSeekTask.
+ }
+}
+
+RefPtr<NextFrameSeekTask::SeekTaskPromise>
+NextFrameSeekTask::Seek(const media::TimeUnit&)
+{
+ AssertOwnerThread();
+
+ DropAllMediaDataBeforeCurrentPosition(mAudioQueue, mVideoQueue,
+ mCurrentTimeBeforeSeek);
+
+ // While creating this seek task object, MDSM might had already ask the
+ // wrapper to decode a media sample or the MDSM is waiting a media data.
+ // If so, we cannot resolve the SeekTaskPromise immediately because there is
+ // a latency of running the resolving runnable. Instead, if there is a pending
+ // media request, we wait for it.
+ if ((mVideoQueue.GetSize() > 0
+ && !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData()
+ && !mReader->IsRequestingVideoData() && !mReader->IsWaitingVideoData())
+ || mVideoQueue.AtEndOfStream()) {
+ UpdateSeekTargetTime();
+ SeekTaskResolveValue val = {}; // Zero-initialize data members.
+ return SeekTask::SeekTaskPromise::CreateAndResolve(val, __func__);
+ } else {
+ // Only invoke EnsureVideoDecodeTaskQueued() if we have no video data; we
+ // might be here because we are waiting audio data, and don't bother to make
+ // more requests to reader in this case.
+ if (mVideoQueue.GetSize() == 0) {
+ EnsureVideoDecodeTaskQueued();
+ }
+ return mSeekTaskPromise.Ensure(__func__);
+ }
+}
+
+bool
+NextFrameSeekTask::IsVideoDecoding() const
+{
+ AssertOwnerThread();
+ return HasVideo() && !mIsVideoQueueFinished;
+}
+
+nsresult
+NextFrameSeekTask::EnsureVideoDecodeTaskQueued()
+{
+ AssertOwnerThread();
+ SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s",
+ IsVideoDecoding(), VideoRequestStatus());
+
+ if (!IsVideoDecoding() ||
+ mReader->IsRequestingVideoData() ||
+ mReader->IsWaitingVideoData()) {
+ return NS_OK;
+ }
+
+ RequestVideoData();
+ return NS_OK;
+}
+
+const char*
+NextFrameSeekTask::VideoRequestStatus()
+{
+ AssertOwnerThread();
+
+ if (mReader->IsRequestingVideoData()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingVideoData());
+ return "pending";
+ } else if (mReader->IsWaitingVideoData()) {
+ return "waiting";
+ }
+ return "idle";
+}
+
+void
+NextFrameSeekTask::RequestVideoData()
+{
+ AssertOwnerThread();
+ SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o",
+ !!mSeekedVideoData, mReader->SizeOfVideoQueueInFrames());
+
+ mReader->RequestVideoData(false, media::TimeUnit());
+}
+
+bool
+NextFrameSeekTask::IsAudioSeekComplete()
+{
+ AssertOwnerThread();
+ SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d aqFin=%d aqSz=%d req=%d wait=%d",
+ mSeekJob.Exists(), mIsAudioQueueFinished, !!mSeekedAudioData,
+ mReader->IsRequestingAudioData(), mReader->IsWaitingAudioData());
+
+ // Just make sure that we are not requesting or waiting for audio data. We
+ // don't really need to get an decoded audio data or get EOS here.
+ return
+ !HasAudio() ||
+ (Exists() && !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData());
+}
+
+bool
+NextFrameSeekTask::IsVideoSeekComplete()
+{
+ AssertOwnerThread();
+ SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d vqFin=%d vqSz=%d",
+ mSeekJob.Exists(), mIsVideoQueueFinished, !!mSeekedVideoData);
+
+ return
+ !HasVideo() || (Exists() && (mIsVideoQueueFinished || mSeekedVideoData));
+}
+
+void
+NextFrameSeekTask::CheckIfSeekComplete()
+{
+ AssertOwnerThread();
+
+ const bool audioSeekComplete = IsAudioSeekComplete();
+
+ const bool videoSeekComplete = IsVideoSeekComplete();
+ if (HasVideo() && !videoSeekComplete) {
+ // We haven't reached the target. Ensure we have requested another sample.
+ if (NS_FAILED(EnsureVideoDecodeTaskQueued())) {
+ DECODER_WARN("Failed to request video during seek");
+ RejectIfExist(__func__);
+ }
+ }
+
+ SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d",
+ audioSeekComplete, videoSeekComplete);
+
+ if (audioSeekComplete && videoSeekComplete) {
+ UpdateSeekTargetTime();
+ Resolve(__func__); // Call to MDSM::SeekCompleted();
+ }
+}
+
+void
+NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample)
+{
+ AssertOwnerThread();
+ MOZ_ASSERT(aAudioSample);
+
+ // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is
+ // resolved.
+
+ SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d",
+ (aAudioSample ? aAudioSample->mTime : -1),
+ (aAudioSample ? aAudioSample->GetEndTime() : -1),
+ (aAudioSample ? aAudioSample->mDiscontinuity : 0));
+
+ if (!Exists()) {
+ // We've received a sample from a previous decode. Discard it.
+ return;
+ }
+
+ // We accept any audio data here.
+ mSeekedAudioData = aAudioSample;
+
+ CheckIfSeekComplete();
+}
+
+void
+NextFrameSeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+{
+ AssertOwnerThread();
+ SAMPLE_LOG("OnAudioNotDecoded (aReason=%u)", aReason);
+
+ if (!Exists()) {
+ // We've received a sample from a previous decode. Discard it.
+ return;
+ }
+
+ // We don't really handle audio deocde error here. Let MDSM to trigger further
+ // audio decoding tasks if it needs to play audio, and MDSM will then receive
+ // the decoding state from MediaDecoderReader.
+
+ CheckIfSeekComplete();
+}
+
+void
+NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample)
+{
+ AssertOwnerThread();
+ MOZ_ASSERT(aVideoSample);
+
+ // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is
+ // resolved.
+
+ SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
+ (aVideoSample ? aVideoSample->mTime : -1),
+ (aVideoSample ? aVideoSample->GetEndTime() : -1),
+ (aVideoSample ? aVideoSample->mDiscontinuity : 0));
+
+ if (!Exists()) {
+ // We've received a sample from a previous decode. Discard it.
+ return;
+ }
+
+ if (aVideoSample->mTime > mCurrentTimeBeforeSeek) {
+ mSeekedVideoData = aVideoSample;
+ }
+
+ CheckIfSeekComplete();
+}
+
+void
+NextFrameSeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+{
+ AssertOwnerThread();
+ SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason);
+
+ if (!Exists()) {
+ // We've received a sample from a previous decode. Discard it.
+ return;
+ }
+
+ if (aReason == MediaDecoderReader::DECODE_ERROR) {
+ if (mVideoQueue.GetSize() > 0) {
+ // The video decoding request might be filed by MDSM not the
+ // NextFrameSeekTask itself. So, the NextFrameSeekTask might has already
+ // found its target in the VideoQueue but still waits the video decoding
+ // request (which is filed by the MDSM) to be resolved. In this case, we
+ // already have the target of this seek task, try to resolve this task.
+ CheckIfSeekComplete();
+ return;
+ }
+
+ // Otherwise, we cannot get the target video frame of this seek task,
+ // delegate the decode error to the generic error path.
+ RejectIfExist(__func__);
+ return;
+ }
+
+ // If the decoder is waiting for data, we tell it to call us back when the
+ // data arrives.
+ if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
+ MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+ "Readers that send WAITING_FOR_DATA need to implement WaitForData");
+ mReader->WaitForData(MediaData::VIDEO_DATA);
+
+ // We are out of data to decode and will enter buffering mode soon.
+ // We want to play the frames we have already decoded, so we stop pre-rolling
+ // and ensure that loadeddata is fired as required.
+ mNeedToStopPrerollingVideo = true;
+ return;
+ }
+
+ if (aReason == MediaDecoderReader::CANCELED) {
+ EnsureVideoDecodeTaskQueued();
+ return;
+ }
+
+ if (aReason == MediaDecoderReader::END_OF_STREAM) {
+ mIsVideoQueueFinished = true;
+ CheckIfSeekComplete();
+ }
+}
+
+void
+NextFrameSeekTask::SetMediaDecoderReaderWrapperCallback()
+{
+ AssertOwnerThread();
+
+ // Register dummy callbcak for audio decoding since we don't need to handle
+ // the decoded audio samples.
+ mAudioCallbackID =
+ mReader->SetAudioCallback(this, &NextFrameSeekTask::OnAudioDecoded,
+ &NextFrameSeekTask::OnAudioNotDecoded);
+
+ mVideoCallbackID =
+ mReader->SetVideoCallback(this, &NextFrameSeekTask::OnVideoDecoded,
+ &NextFrameSeekTask::OnVideoNotDecoded);
+
+ RefPtr<NextFrameSeekTask> self = this;
+ mWaitAudioCallbackID =
+ mReader->SetWaitAudioCallback(
+ [self] (MediaData::Type aType) -> void {
+ self->AssertOwnerThread();
+ // We don't make an audio decode request here, instead, let MDSM to
+ // trigger further audio decode tasks if MDSM itself needs to play audio.
+ },
+ [self] (WaitForDataRejectValue aRejection) -> void {
+ self->AssertOwnerThread();
+ });
+
+ mWaitVideoCallbackID =
+ mReader->SetWaitVideoCallback(
+ [self] (MediaData::Type aType) -> void {
+ self->AssertOwnerThread();
+ self->EnsureVideoDecodeTaskQueued();
+ },
+ [self] (WaitForDataRejectValue aRejection) -> void {
+ self->AssertOwnerThread();
+ });
+
+ DECODER_LOG("NextFrameSeekTask set audio callbacks: mVideoCallbackID = %d\n", (int)mAudioCallbackID);
+ DECODER_LOG("NextFrameSeekTask set video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID);
+ DECODER_LOG("NextFrameSeekTask set wait audio callbacks: mWaitAudioCallbackID = %d\n", (int)mWaitAudioCallbackID);
+ DECODER_LOG("NextFrameSeekTask set wait video callbacks: mWaitVideoCallbackID = %d\n", (int)mWaitVideoCallbackID);
+}
+
+void
+NextFrameSeekTask::CancelMediaDecoderReaderWrapperCallback()
+{
+ AssertOwnerThread();
+
+ DECODER_LOG("NextFrameSeekTask cancel audio callbacks: mAudioCallbackID = %d\n", (int)mAudioCallbackID);
+ mReader->CancelAudioCallback(mAudioCallbackID);
+
+ DECODER_LOG("NextFrameSeekTask cancel video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID);
+ mReader->CancelVideoCallback(mVideoCallbackID);
+
+ DECODER_LOG("NextFrameSeekTask cancel wait audio callbacks: mWaitAudioCallbackID = %d\n", (int)mWaitAudioCallbackID);
+ mReader->CancelWaitAudioCallback(mWaitAudioCallbackID);
+
+ DECODER_LOG("NextFrameSeekTask cancel wait video callbacks: mWaitVideoCallbackID = %d\n", (int)mWaitVideoCallbackID);
+ mReader->CancelWaitVideoCallback(mWaitVideoCallbackID);
+}
+
+void
+NextFrameSeekTask::UpdateSeekTargetTime()
+{
+ AssertOwnerThread();
+
+ RefPtr<MediaData> data = mVideoQueue.PeekFront();
+ if (data) {
+ mSeekJob.mTarget.SetTime(TimeUnit::FromMicroseconds(data->mTime));
+ } else if (mSeekedVideoData) {
+ mSeekJob.mTarget.SetTime(TimeUnit::FromMicroseconds(mSeekedVideoData->mTime));
+ } else if (mIsVideoQueueFinished || mVideoQueue.AtEndOfStream()) {
+ mSeekJob.mTarget.SetTime(mDuration);
+ } else {
+ MOZ_ASSERT(false, "No data!");
+ }
+}
+} // namespace media
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/NextFrameSeekTask.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NEXTFRAME_SEEK_TASK_H
+#define NEXTFRAME_SEEK_TASK_H
+
+#include "SeekTask.h"
+#include "MediaDecoderReader.h"
+
+namespace mozilla {
+namespace media {
+
+/*
+ * While invoking a NextFrameSeekTask, we don't know the seek target time, what
+ * we know is the media's currant position. We use the media's currant position
+ * to find out what the next frame is, by traversing through the video queue or
+ * asking the decoder to decode more video frames. Once we confirm the next
+ * frame, we then know the target time of the NextFrameSeekTask and we update it
+ * so that the MDSM will be able to update the media element's position.
+ */
+
+class NextFrameSeekTask final : public SeekTask {
+public:
+ NextFrameSeekTask(const void* aDecoderID,
+ AbstractThread* aThread,
+ MediaDecoderReaderWrapper* aReader,
+ SeekJob&& aSeekJob,
+ const MediaInfo& aInfo,
+ const media::TimeUnit& aDuration,
+ int64_t aCurrentMediaTime,
+ MediaQueue<MediaData>& aAudioQueue,
+ MediaQueue<MediaData>& aVideoQueue);
+
+ void Discard() override;
+
+ RefPtr<SeekTaskPromise> Seek(const media::TimeUnit& aDuration) override;
+
+ bool NeedToResetMDSM() const override;
+
+private:
+ ~NextFrameSeekTask();
+
+ bool HasAudio() const;
+
+ bool HasVideo() const;
+
+ bool IsVideoDecoding() const;
+
+ nsresult EnsureVideoDecodeTaskQueued();
+
+ const char* VideoRequestStatus();
+
+ void RequestVideoData();
+
+ bool IsAudioSeekComplete();
+
+ bool IsVideoSeekComplete();
+
+ void CheckIfSeekComplete();
+
+ void OnAudioDecoded(MediaData* aAudioSample);
+
+ void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+
+ void OnVideoDecoded(MediaData* aVideoSample);
+
+ void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+
+ void SetMediaDecoderReaderWrapperCallback();
+
+ void CancelMediaDecoderReaderWrapperCallback();
+
+ // Update the seek target's time before resolving this seek task, the updated
+ // time will be used in the MDSM::SeekCompleted() to update the MDSM's position.
+ void UpdateSeekTargetTime();
+
+ /*
+ * Data shared with MDSM.
+ */
+ MediaQueue<MediaData>& mAudioQueue;
+ MediaQueue<MediaData>& mVideoQueue;
+
+ /*
+ * Internal state.
+ */
+ const int64_t mCurrentTimeBeforeSeek;
+ const bool mHasAudio;
+ const bool mHasVideo;
+ media::TimeUnit mDuration;
+
+ /*
+ * Track the current seek promise made by the reader.
+ */
+ CallbackID mAudioCallbackID;
+ CallbackID mVideoCallbackID;
+ CallbackID mWaitAudioCallbackID;
+ CallbackID mWaitVideoCallbackID;
+};
+
+} // namespace media
+} // namespace mozilla
+
+#endif /* NEXTFRAME_SEEK_TASK_H */
--- a/dom/media/SeekTarget.h
+++ b/dom/media/SeekTarget.h
@@ -20,16 +20,17 @@ enum class MediaDecoderEventVisibility :
// "Fast" (nearest keyframe), or "Video Only" (no audio seek) seek was
// requested.
struct SeekTarget {
enum Type {
Invalid,
PrevSyncPoint,
Accurate,
AccurateVideoOnly,
+ NextFrame,
};
SeekTarget()
: mEventVisibility(MediaDecoderEventVisibility::Observable)
, mTime(media::TimeUnit::Invalid())
, mType(SeekTarget::Invalid)
{
}
SeekTarget(int64_t aTimeUsecs,
@@ -78,16 +79,19 @@ struct SeekTarget {
return mType == SeekTarget::Type::PrevSyncPoint;
}
bool IsAccurate() const {
return mType == SeekTarget::Type::Accurate;
}
bool IsVideoOnly() const {
return mType == SeekTarget::Type::AccurateVideoOnly;
}
+ bool IsNextFrame() const {
+ return mType == SeekTarget::Type::NextFrame;
+ }
MediaDecoderEventVisibility mEventVisibility;
private:
// Seek target time.
media::TimeUnit mTime;
// Whether we should seek "Fast", or "Accurate".
// "Fast" seeks to the seek point preceding mTime, whereas
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -129,16 +129,17 @@ EXPORTS += [
'MediaStatistics.h',
'MediaStreamGraph.h',
'MediaTimer.h',
'MediaTrack.h',
'MediaTrackList.h',
'MP3Decoder.h',
'MP3Demuxer.h',
'MP3FrameParser.h',
+ 'NextFrameSeekTask.h',
'nsIDocumentActivity.h',
'PrincipalChangeObserver.h',
'QueueObject.h',
'RtspMediaResource.h',
'SeekJob.h',
'SeekTarget.h',
'SeekTask.h',
'SelfRef.h',
@@ -241,16 +242,17 @@ UNIFIED_SOURCES += [
'MediaStreamGraph.cpp',
'MediaStreamTrack.cpp',
'MediaTimer.cpp',
'MediaTrack.cpp',
'MediaTrackList.cpp',
'MP3Decoder.cpp',
'MP3Demuxer.cpp',
'MP3FrameParser.cpp',
+ 'NextFrameSeekTask.cpp',
'QueueObject.cpp',
'RtspMediaResource.cpp',
'SeekJob.cpp',
'SeekTask.cpp',
'StreamTracks.cpp',
'TextTrack.cpp',
'TextTrackCue.cpp',
'TextTrackCueList.cpp',