--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -189,21 +189,20 @@ public:
// Returns the number of bytes of memory allocated by structures/frames in
// the audio queue.
size_t SizeOfAudioQueueInBytes() const;
virtual size_t SizeOfVideoQueueInFrames();
virtual size_t SizeOfAudioQueueInFrames();
- void NotifyDataArrived()
+ virtual void NotifyDataArrived()
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE_VOID(!mShutdown);
- NotifyDataArrivedInternal();
UpdateBuffered();
}
virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
virtual MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
AbstractCanonical<media::TimeIntervals>* CanonicalBuffered()
{
@@ -249,34 +248,16 @@ public:
virtual void SetVideoBlankDecode(bool aIsBlankDecode) {}
protected:
virtual ~MediaDecoderReader();
// Recomputes mBuffered.
virtual void UpdateBuffered();
- // Populates aBuffered with the time ranges which are buffered. This may only
- // be called on the decode task queue, and should only be used internally by
- // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
- // else.
- //
- // This base implementation in MediaDecoderReader estimates the time ranges
- // buffered by interpolating the cached byte ranges with the duration
- // of the media. Reader subclasses should override this method if they
- // can quickly calculate the buffered ranges more accurately.
- //
- // The primary advantage of this implementation in the reader base class
- // is that it's a fast approximation, which does not perform any I/O.
- //
- // The OggReader relies on this base implementation not performing I/O,
- // since in FirefoxOS we can't do I/O on the main thread, where this is
- // called.
- virtual media::TimeIntervals GetBuffered();
-
RefPtr<MediaDataPromise> DecodeToFirstVideoData();
// Queue of audio frames. This queue is threadsafe, and is accessed from
// the audio, decoder, state machine, and main threads.
MediaQueue<AudioData> mAudioQueue;
// Queue of video frames. This queue is threadsafe, and is accessed from
// the decoder, state machine, and main threads.
@@ -339,18 +320,16 @@ private:
// Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
{
MOZ_CRASH();
}
virtual void VisibilityChanged();
- virtual void NotifyDataArrivedInternal() {}
-
// Overrides of this function should decodes an unspecified amount of
// audio data, enqueuing the audio data in mAudioQueue. Returns true
// when there's more audio to decode, false if the audio is finished,
// end of file has been reached, or an un-recoverable read error has
// occured. This function blocks until the decode is complete.
virtual bool DecodeAudioData()
{
return false;
@@ -361,16 +340,34 @@ private:
// (unless they're not keyframes and aKeyframeSkip is true), but will
// not be added to the queue. This function blocks until the decode
// is complete.
virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold)
{
return false;
}
+ // Populates aBuffered with the time ranges which are buffered. This may only
+ // be called on the decode task queue, and should only be used internally by
+ // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
+ // else.
+ //
+ // This base implementation in MediaDecoderReader estimates the time ranges
+ // buffered by interpolating the cached byte ranges with the duration
+ // of the media. Reader subclasses should override this method if they
+ // can quickly calculate the buffered ranges more accurately.
+ //
+ // The primary advantage of this implementation in the reader base class
+ // is that it's a fast approximation, which does not perform any I/O.
+ //
+ // The OggReader relies on this base implementation not performing I/O,
+ // since in FirefoxOS we can't do I/O on the main thread, where this is
+ // called.
+ media::TimeIntervals GetBuffered();
+
// Promises used only for the base-class (sync->async adapter) implementation
// of Request{Audio,Video}Data.
MozPromiseHolder<MediaDataPromise> mBaseAudioPromise;
MozPromiseHolder<MediaDataPromise> mBaseVideoPromise;
MediaEventListener mDataArrivedListener;
};
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1,32 +1,35 @@
/* -*- 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 "mozilla/CDMProxy.h"
-#include "mozilla/ClearOnShutdown.h"
-#include "mozilla/dom/HTMLMediaElement.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/Telemetry.h"
-#include "nsContentUtils.h"
-#include "nsPrintfCString.h"
-#include "nsSize.h"
+#include "AutoTaskQueue.h"
#include "Layers.h"
#include "MediaData.h"
#include "MediaInfo.h"
#include "MediaFormatReader.h"
#include "MediaPrefs.h"
#include "MediaResource.h"
-#include "mozilla/SharedThreadPool.h"
#include "VideoUtils.h"
#include "VideoFrameContainer.h"
+#include "mozilla/dom/HTMLMediaElement.h"
#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
+#include "nsSize.h"
#include <algorithm>
#include <queue>
using namespace mozilla::media;
using mozilla::layers::Image;
using mozilla::layers::LayerManager;
@@ -422,16 +425,349 @@ MediaFormatReader::DecoderFactory::DoIni
data.mInitPromise.Complete();
data.mStage = Stage::None;
data.mDecoder->Shutdown();
data.mDecoder = nullptr;
mOwner->NotifyError(aTrack, aError);
}));
}
+// DemuxerProxy ensures that the original main demuxer is only ever accessed
+// via its own dedicated task queue.
+// This ensure that the reader's taskqueue will never blocked while a demuxer
+// is itself blocked attempting to access the MediaCache or the MediaResource.
+class MediaFormatReader::DemuxerProxy
+{
+ using TrackType = TrackInfo::TrackType;
+ class Wrapper;
+
+public:
+ explicit DemuxerProxy(MediaDataDemuxer* aDemuxer)
+ : mTaskQueue(new AutoTaskQueue(
+ GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER)))
+ , mData(new Data(aDemuxer))
+ {
+ MOZ_COUNT_CTOR(DemuxerProxy);
+ }
+
+ ~DemuxerProxy()
+ {
+ MOZ_COUNT_DTOR(DemuxerProxy);
+ mData->mAudioDemuxer = nullptr;
+ mData->mVideoDemuxer = nullptr;
+ RefPtr<Data> data = mData.forget();
+ mTaskQueue->Dispatch(
+ // We need to clear our reference to the demuxer now. So that in the event
+ // the init promise wasn't resolved, such as what can happen with the
+ // mediasource demuxer that is waiting on more data, it will force the
+ // init promise to be rejected.
+ NS_NewRunnableFunction([data]() { data->mDemuxer = nullptr; }));
+ }
+
+ RefPtr<MediaDataDemuxer::InitPromise> Init();
+
+ Wrapper*
+ GetTrackDemuxer(TrackType aTrack, uint32_t aTrackNumber)
+ {
+ MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+ switch (aTrack) {
+ case TrackInfo::kAudioTrack:
+ return mData->mAudioDemuxer;
+ case TrackInfo::kVideoTrack:
+ return mData->mVideoDemuxer;
+ default:
+ return nullptr;
+ }
+ }
+
+ uint32_t GetNumberTracks(TrackType aTrack) const
+ {
+ MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+ switch (aTrack) {
+ case TrackInfo::kAudioTrack:
+ return mData->mNumAudioTrack;
+ case TrackInfo::kVideoTrack:
+ return mData->mNumVideoTrack;
+ default:
+ return 0;
+ }
+ }
+
+ bool IsSeekable() const
+ {
+ MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+ return mData->mSeekable;
+ }
+
+ bool IsSeekableOnlyInBufferedRanges() const
+ {
+ MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+ return mData->mSeekableOnlyInBufferedRange;
+ }
+
+ UniquePtr<EncryptionInfo> GetCrypto() const
+ {
+ MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+ if (!mData->mCrypto) {
+ return nullptr;
+ }
+ auto crypto = MakeUnique<EncryptionInfo>();
+ *crypto = *mData->mCrypto;
+ return crypto;
+ }
+
+ RefPtr<NotifyDataArrivedPromise> NotifyDataArrived();
+
+ bool ShouldComputeStartTime() const
+ {
+ MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+ return mData->mShouldComputeStartTime;
+ }
+
+private:
+ const RefPtr<AutoTaskQueue> mTaskQueue;
+ struct Data
+ {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Data)
+
+ explicit Data(MediaDataDemuxer* aDemuxer)
+ : mInitDone(false)
+ , mDemuxer(aDemuxer)
+ { }
+
+ Atomic<bool> mInitDone;
+ // Only ever accessed over mTaskQueue once.
+ RefPtr<MediaDataDemuxer> mDemuxer;
+ // Only accessed once InitPromise has been resolved and immutable after.
+ // So we can safely access them without the use of the mutex.
+ uint32_t mNumAudioTrack = 0;
+ RefPtr<Wrapper> mAudioDemuxer;
+ uint32_t mNumVideoTrack = 0;
+ RefPtr<Wrapper> mVideoDemuxer;
+ bool mSeekable = false;
+ bool mSeekableOnlyInBufferedRange = false;
+ bool mShouldComputeStartTime = true;
+ UniquePtr<EncryptionInfo> mCrypto;
+ private:
+ ~Data() { }
+ };
+ RefPtr<Data> mData;
+};
+
+class MediaFormatReader::DemuxerProxy::Wrapper : public MediaTrackDemuxer
+{
+public:
+ Wrapper(MediaTrackDemuxer* aTrackDemuxer, AutoTaskQueue* aTaskQueue)
+ : mMutex("TrackDemuxer Mutex")
+ , mTaskQueue(aTaskQueue)
+ , mGetSamplesMayBlock(aTrackDemuxer->GetSamplesMayBlock())
+ , mInfo(aTrackDemuxer->GetInfo())
+ , mTrackDemuxer(aTrackDemuxer)
+ { }
+
+ UniquePtr<TrackInfo> GetInfo() const override
+ {
+ if (!mInfo) {
+ return nullptr;
+ }
+ return mInfo->Clone();
+ }
+
+ RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override
+ {
+ RefPtr<Wrapper> self = this;
+ return InvokeAsync(
+ mTaskQueue, __func__,
+ [self, aTime]() { return self->mTrackDemuxer->Seek(aTime); })
+ ->Then(mTaskQueue, __func__,
+ [self]() { self->UpdateRandomAccessPoint(); },
+ [self]() { self->UpdateRandomAccessPoint(); });
+ }
+
+ RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples) override
+ {
+ RefPtr<Wrapper> self = this;
+ return InvokeAsync(mTaskQueue, __func__,
+ [self, aNumSamples]() {
+ return self->mTrackDemuxer->GetSamples(aNumSamples);
+ })
+ ->Then(mTaskQueue, __func__,
+ [self]() { self->UpdateRandomAccessPoint(); },
+ [self]() { self->UpdateRandomAccessPoint(); });
+ }
+
+ bool GetSamplesMayBlock() const override
+ {
+ return mGetSamplesMayBlock;
+ }
+
+ void Reset() override
+ {
+ RefPtr<Wrapper> self = this;
+ mTaskQueue->Dispatch(NS_NewRunnableFunction([self]() {
+ self->mTrackDemuxer->Reset();
+ }));
+ }
+
+ nsresult GetNextRandomAccessPoint(TimeUnit* aTime) override
+ {
+ MutexAutoLock lock(mMutex);
+ if (NS_SUCCEEDED(mNextRandomAccessPointResult)) {
+ *aTime = mNextRandomAccessPoint;
+ }
+ return mNextRandomAccessPointResult;
+ }
+
+ RefPtr<SkipAccessPointPromise>
+ SkipToNextRandomAccessPoint(const media::TimeUnit& aTimeThreshold) override
+ {
+ RefPtr<Wrapper> self = this;
+ return InvokeAsync(
+ mTaskQueue, __func__,
+ [self, aTimeThreshold]() {
+ return self->mTrackDemuxer->SkipToNextRandomAccessPoint(
+ aTimeThreshold);
+ })
+ ->Then(mTaskQueue, __func__,
+ [self]() { self->UpdateRandomAccessPoint(); },
+ [self]() { self->UpdateRandomAccessPoint(); });
+ }
+
+ TimeIntervals GetBuffered() override
+ {
+ MutexAutoLock lock(mMutex);
+ return mBuffered;
+ }
+
+ void BreakCycles() override { }
+
+private:
+ Mutex mMutex;
+ const RefPtr<AutoTaskQueue> mTaskQueue;
+ const bool mGetSamplesMayBlock;
+ const UniquePtr<TrackInfo> mInfo;
+ // mTrackDemuxer is only ever accessed on demuxer's task queue.
+ RefPtr<MediaTrackDemuxer> mTrackDemuxer;
+ // All following members are protected by mMutex
+ nsresult mNextRandomAccessPointResult = NS_OK;
+ TimeUnit mNextRandomAccessPoint;
+ TimeIntervals mBuffered;
+ friend class DemuxerProxy;
+
+ ~Wrapper()
+ {
+ RefPtr<MediaTrackDemuxer> trackDemuxer = mTrackDemuxer.forget();
+ mTaskQueue->Dispatch(NS_NewRunnableFunction(
+ [trackDemuxer]() { trackDemuxer->BreakCycles(); }));
+ }
+
+ void UpdateRandomAccessPoint()
+ {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (!mTrackDemuxer) {
+ // Detached.
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ mNextRandomAccessPointResult =
+ mTrackDemuxer->GetNextRandomAccessPoint(&mNextRandomAccessPoint);
+ }
+
+ void UpdateBuffered()
+ {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (!mTrackDemuxer) {
+ // Detached.
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ mBuffered = mTrackDemuxer->GetBuffered();
+ }
+};
+
+RefPtr<MediaDataDemuxer::InitPromise>
+MediaFormatReader::DemuxerProxy::Init()
+{
+ RefPtr<Data> data = mData;
+ RefPtr<AutoTaskQueue> taskQueue = mTaskQueue;
+ return InvokeAsync(mTaskQueue, __func__,
+ [data, taskQueue]() {
+ if (!data->mDemuxer) {
+ return MediaDataDemuxer::InitPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ }
+ return data->mDemuxer->Init();
+ })
+ ->Then(taskQueue, __func__,
+ [data, taskQueue]() {
+ if (!data->mDemuxer) { // Was shutdown.
+ return;
+ }
+ data->mNumAudioTrack =
+ data->mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
+ if (data->mNumAudioTrack) {
+ RefPtr<MediaTrackDemuxer> d =
+ data->mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
+ if (d) {
+ RefPtr<Wrapper> wrapper =
+ new DemuxerProxy::Wrapper(d, taskQueue);
+ wrapper->UpdateBuffered();
+ data->mAudioDemuxer = wrapper;
+ }
+ }
+ data->mNumVideoTrack =
+ data->mDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
+ if (data->mNumVideoTrack) {
+ RefPtr<MediaTrackDemuxer> d =
+ data->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
+ if (d) {
+ RefPtr<Wrapper> wrapper =
+ new DemuxerProxy::Wrapper(d, taskQueue);
+ wrapper->UpdateBuffered();
+ data->mVideoDemuxer = wrapper;
+ }
+ }
+ data->mCrypto = data->mDemuxer->GetCrypto();
+ data->mSeekable = data->mDemuxer->IsSeekable();
+ data->mSeekableOnlyInBufferedRange =
+ data->mDemuxer->IsSeekableOnlyInBufferedRanges();
+ data->mShouldComputeStartTime =
+ data->mDemuxer->ShouldComputeStartTime();
+ data->mInitDone = true;
+ },
+ []() {});
+}
+
+RefPtr<MediaFormatReader::NotifyDataArrivedPromise>
+MediaFormatReader::DemuxerProxy::NotifyDataArrived()
+{
+ RefPtr<Data> data = mData;
+ return InvokeAsync(mTaskQueue, __func__, [data]() {
+ if (!data->mDemuxer) {
+ // Was shutdown.
+ return NotifyDataArrivedPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ }
+ data->mDemuxer->NotifyDataArrived();
+ if (data->mAudioDemuxer) {
+ data->mAudioDemuxer->UpdateBuffered();
+ }
+ if (data->mVideoDemuxer) {
+ data->mVideoDemuxer->UpdateBuffered();
+ }
+ return NotifyDataArrivedPromise::CreateAndResolve(true, __func__);
+ });
+}
+
static const char*
TrackTypeToStr(TrackInfo::TrackType aTrack)
{
MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
aTrack == TrackInfo::kVideoTrack ||
aTrack == TrackInfo::kTextTrack);
switch (aTrack) {
case TrackInfo::kAudioTrack:
@@ -448,17 +784,17 @@ TrackTypeToStr(TrackInfo::TrackType aTra
MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
MediaDataDemuxer* aDemuxer,
VideoFrameContainer* aVideoFrameContainer)
: MediaDecoderReader(aDecoder)
, mAudio(this, MediaData::AUDIO_DATA,
Preferences::GetUint("media.audio-max-decode-error", 3))
, mVideo(this, MediaData::VIDEO_DATA,
Preferences::GetUint("media.video-max-decode-error", 2))
- , mDemuxer(aDemuxer)
+ , mDemuxer(new DemuxerProxy(aDemuxer))
, mDemuxerInitDone(false)
, mLastReportedNumDecodedFrames(0)
, mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
, mInitDone(false)
, mTrackDemuxersMayBlock(false)
, mSeekScheduled(false)
, mVideoFrameContainer(aVideoFrameContainer)
, mDecoderFactory(new DecoderFactory(this))
@@ -480,16 +816,17 @@ MediaFormatReader::~MediaFormatReader()
RefPtr<ShutdownPromise>
MediaFormatReader::Shutdown()
{
MOZ_ASSERT(OnTaskQueue());
mDecoderFactory = nullptr;
mDemuxerInitRequest.DisconnectIfExists();
+ mNotifyDataArrivedPromise.DisconnectIfExists();
mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
mSkipRequest.DisconnectIfExists();
if (mAudio.mDecoder) {
Reset(TrackInfo::kAudioTrack);
if (mAudio.HasPromise()) {
mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
@@ -684,17 +1021,16 @@ MediaFormatReader::OnDemuxerInitDone(nsr
return;
}
mInfo.mVideo = *videoInfo->GetAsVideoInfo();
for (const MetadataTag& tag : videoInfo->mTags) {
tags->Put(tag.mKey, tag.mValue);
}
mVideo.mOriginalInfo = Move(videoInfo);
mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack);
- mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
mTrackDemuxersMayBlock |= mVideo.mTrackDemuxer->GetSamplesMayBlock();
} else {
mVideo.mTrackDemuxer->BreakCycles();
mVideo.mTrackDemuxer = nullptr;
}
}
bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
@@ -713,17 +1049,16 @@ MediaFormatReader::OnDemuxerInitDone(nsr
if (audioActive) {
mInfo.mAudio = *audioInfo->GetAsAudioInfo();
for (const MetadataTag& tag : audioInfo->mTags) {
tags->Put(tag.mKey, tag.mValue);
}
mAudio.mOriginalInfo = Move(audioInfo);
mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack);
- mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
mTrackDemuxersMayBlock |= mAudio.mTrackDemuxer->GetSamplesMayBlock();
} else {
mAudio.mTrackDemuxer->BreakCycles();
mAudio.mTrackDemuxer = nullptr;
}
}
UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
@@ -789,22 +1124,25 @@ MediaFormatReader::MaybeResolveMetadataP
TimeUnit startTime =
std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));
if (!startTime.IsInfinite()) {
mInfo.mStartTime = startTime; // mInfo.mStartTime is initialized to 0.
}
+ RefPtr<MetadataHolder> metadata = new MetadataHolder();
+ metadata->mInfo = mInfo;
+ metadata->mTags = mTags->Count() ? mTags.release() : nullptr;
+
+ // We now have all the informations required to calculate the initial buffered
+ // range.
mHasStartTime = true;
UpdateBuffered();
- RefPtr<MetadataHolder> metadata = new MetadataHolder();
- metadata->mInfo = mInfo;
- metadata->mTags = mTags->Count() ? mTags.release() : nullptr;
mMetadataPromise.Resolve(metadata, __func__);
}
bool
MediaFormatReader::IsEncrypted() const
{
return (HasAudio() && mInfo.mAudio.mCrypto.mValid) ||
(HasVideo() && mInfo.mVideo.mCrypto.mValid);
@@ -1150,19 +1488,16 @@ MediaFormatReader::UpdateReceivedNewData
{
MOZ_ASSERT(OnTaskQueue());
auto& decoder = GetDecoderData(aTrack);
if (!decoder.mReceivedNewData) {
return false;
}
- // Update our cached TimeRange.
- decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
-
// We do not want to clear mWaitingForData while there are pending
// demuxing or seeking operations that could affect the value of this flag.
// This is in order to ensure that we will retry once they complete as we may
// now have new data that could potentially allow those operations to
// successfully complete if tried again.
if (decoder.mSeekRequest.Exists()) {
// Nothing more to do until this operation complete.
return true;
@@ -1180,26 +1515,16 @@ MediaFormatReader::UpdateReceivedNewData
}
if (decoder.HasPendingDrain()) {
// We do not want to clear mWaitingForData or mDemuxEOS while
// a drain is in progress in order to properly complete the operation.
return false;
}
- bool hasLastEnd;
- media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
- if (hasLastEnd) {
- if (decoder.mLastTimeRangesEnd && decoder.mLastTimeRangesEnd.ref() < lastEnd) {
- // New data was added after our previous end, we can clear the EOS flag.
- decoder.mDemuxEOS = false;
- }
- decoder.mLastTimeRangesEnd = Some(lastEnd);
- }
-
decoder.mReceivedNewData = false;
if (decoder.mTimeThreshold) {
decoder.mTimeThreshold.ref().mWaiting = false;
}
decoder.mWaitingForData = false;
if (decoder.HasFatalError()) {
return false;
@@ -2181,101 +2506,138 @@ MediaFormatReader::OnAudioSeekCompleted(
}
void
MediaFormatReader::OnAudioSeekFailed(const MediaResult& aError)
{
OnSeekFailed(TrackType::kAudioTrack, aError);
}
-media::TimeIntervals
-MediaFormatReader::GetBuffered()
-{
- MOZ_ASSERT(OnTaskQueue());
- media::TimeIntervals videoti;
- media::TimeIntervals audioti;
- media::TimeIntervals intervals;
-
- if (!mInitDone || !mHasStartTime) {
- return intervals;
- }
-
- // Ensure we have up to date buffered time range.
- if (HasVideo()) {
- UpdateReceivedNewData(TrackType::kVideoTrack);
- }
- if (HasAudio()) {
- UpdateReceivedNewData(TrackType::kAudioTrack);
- }
- if (HasVideo()) {
- videoti = mVideo.mTimeRanges;
- }
- if (HasAudio()) {
- audioti = mAudio.mTimeRanges;
- }
- if (HasAudio() && HasVideo()) {
- intervals = media::Intersection(Move(videoti), Move(audioti));
- } else if (HasAudio()) {
- intervals = Move(audioti);
- } else if (HasVideo()) {
- intervals = Move(videoti);
- }
-
- if (!intervals.Length() ||
- intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
- // IntervalSet already starts at 0 or is empty, nothing to shift.
- return intervals;
- }
- return intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
-}
-
void MediaFormatReader::ReleaseResources()
{
mVideo.ShutdownDecoder();
mAudio.ShutdownDecoder();
}
bool
MediaFormatReader::VideoIsHardwareAccelerated() const
{
return mVideo.mIsHardwareAccelerated;
}
void
-MediaFormatReader::NotifyDemuxer()
+MediaFormatReader::NotifyTrackDemuxers()
{
MOZ_ASSERT(OnTaskQueue());
- if (mShutdown || !mDemuxer ||
- (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
- return;
- }
-
LOGV("");
- mDemuxer->NotifyDataArrived();
-
if (!mInitDone) {
return;
}
+
if (HasVideo()) {
mVideo.mReceivedNewData = true;
ScheduleUpdate(TrackType::kVideoTrack);
}
if (HasAudio()) {
mAudio.mReceivedNewData = true;
ScheduleUpdate(TrackType::kAudioTrack);
}
}
void
-MediaFormatReader::NotifyDataArrivedInternal()
+MediaFormatReader::NotifyDataArrived()
+{
+ MOZ_ASSERT(OnTaskQueue());
+
+ if (mShutdown || !mDemuxer ||
+ (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
+ return;
+ }
+
+ if (mNotifyDataArrivedPromise.Exists()) {
+ // Already one in progress. Reschedule for later.
+ RefPtr<nsIRunnable> task(
+ NewRunnableMethod(this, &MediaFormatReader::NotifyDataArrived));
+ OwnerThread()->Dispatch(task.forget());
+ return;
+ }
+
+ RefPtr<MediaFormatReader> self = this;
+ mNotifyDataArrivedPromise.Begin(mDemuxer->NotifyDataArrived()->Then(
+ OwnerThread(), __func__,
+ [self]() {
+ self->mNotifyDataArrivedPromise.Complete();
+ self->UpdateBuffered();
+ self->NotifyTrackDemuxers();
+ },
+ [self]() { self->mNotifyDataArrivedPromise.Complete(); }));
+}
+
+void
+MediaFormatReader::UpdateBuffered()
{
MOZ_ASSERT(OnTaskQueue());
- NotifyDemuxer();
+
+ if (mShutdown) {
+ return;
+ }
+
+ if (!mInitDone || !mHasStartTime) {
+ mBuffered = TimeIntervals();
+ return;
+ }
+
+ if (HasVideo()) {
+ mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
+ bool hasLastEnd;
+ media::TimeUnit lastEnd = mVideo.mTimeRanges.GetEnd(&hasLastEnd);
+ if (hasLastEnd) {
+ if (mVideo.mLastTimeRangesEnd
+ && mVideo.mLastTimeRangesEnd.ref() < lastEnd) {
+ // New data was added after our previous end, we can clear the EOS flag.
+ mVideo.mDemuxEOS = false;
+ ScheduleUpdate(TrackInfo::kVideoTrack);
+ }
+ mVideo.mLastTimeRangesEnd = Some(lastEnd);
+ }
+ }
+ if (HasAudio()) {
+ mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
+ bool hasLastEnd;
+ media::TimeUnit lastEnd = mAudio.mTimeRanges.GetEnd(&hasLastEnd);
+ if (hasLastEnd) {
+ if (mAudio.mLastTimeRangesEnd
+ && mAudio.mLastTimeRangesEnd.ref() < lastEnd) {
+ // New data was added after our previous end, we can clear the EOS flag.
+ mAudio.mDemuxEOS = false;
+ ScheduleUpdate(TrackInfo::kAudioTrack);
+ }
+ mAudio.mLastTimeRangesEnd = Some(lastEnd);
+ }
+ }
+
+ media::TimeIntervals intervals;
+ if (HasAudio() && HasVideo()) {
+ intervals = media::Intersection(mVideo.mTimeRanges, mAudio.mTimeRanges);
+ } else if (HasAudio()) {
+ intervals = mAudio.mTimeRanges;
+ } else if (HasVideo()) {
+ intervals = mVideo.mTimeRanges;
+ }
+
+ if (!intervals.Length() ||
+ intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
+ // IntervalSet already starts at 0 or is empty, nothing to shift.
+ mBuffered = intervals;
+ } else {
+ mBuffered =
+ intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
+ }
}
bool
MediaFormatReader::ForceZeroStartTime() const
{
return !mDemuxer->ShouldComputeStartTime();
}
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -20,16 +20,17 @@
namespace mozilla {
class CDMProxy;
class MediaFormatReader final : public MediaDecoderReader
{
typedef TrackInfo::TrackType TrackType;
+ typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> NotifyDataArrivedPromise;
public:
MediaFormatReader(AbstractMediaDecoder* aDecoder,
MediaDataDemuxer* aDemuxer,
VideoFrameContainer* aVideoFrameContainer = nullptr);
virtual ~MediaFormatReader();
@@ -44,21 +45,20 @@ public:
RefPtr<MetadataPromise> AsyncReadMetadata() override;
void ReadUpdatedMetadata(MediaInfo* aInfo) override;
RefPtr<SeekPromise>
Seek(const SeekTarget& aTarget, int64_t aUnused) override;
protected:
- void NotifyDataArrivedInternal() override;
+ void NotifyDataArrived() override;
+ void UpdateBuffered() override;
public:
- media::TimeIntervals GetBuffered() override;
-
bool ForceZeroStartTime() const override;
// For Media Resource Management
void ReleaseResources() override;
nsresult ResetDecode(TrackSet aTracks) override;
RefPtr<ShutdownPromise> Shutdown() override;
@@ -87,20 +87,18 @@ private:
nsresult InitInternal() override;
bool HasVideo() const { return mVideo.mTrackDemuxer; }
bool HasAudio() const { return mAudio.mTrackDemuxer; }
bool IsWaitingOnCDMResource();
bool InitDemuxer();
- // Notify the demuxer that new data has been received.
- // The next queued task calling GetBuffered() is guaranteed to have up to date
- // buffered ranges.
- void NotifyDemuxer();
+ // Notify the track demuxers that new data has been received.
+ void NotifyTrackDemuxers();
void ReturnOutput(MediaData* aData, TrackType aTrack);
// Enqueues a task to call Update(aTrack) on the decoder task queue.
// Lock for corresponding track must be held.
void ScheduleUpdate(TrackType aTrack);
void Update(TrackType aTrack);
// Handle actions should more data be received.
// Returns true if no more action is required.
@@ -477,22 +475,24 @@ private:
DecoderDataWithPromise mVideo;
// Returns true when the decoder for this track needs input.
bool NeedInput(DecoderData& aDecoder);
DecoderData& GetDecoderData(TrackType aTrack);
// Demuxer objects.
- RefPtr<MediaDataDemuxer> mDemuxer;
+ class DemuxerProxy;
+ UniquePtr<DemuxerProxy> mDemuxer;
bool mDemuxerInitDone;
void OnDemuxerInitDone(nsresult);
void OnDemuxerInitFailed(const MediaResult& aError);
MozPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest;
- void OnDemuxFailed(TrackType aTrack, const MediaResult& aError);
+ MozPromiseRequestHolder<NotifyDataArrivedPromise> mNotifyDataArrivedPromise;
+ void OnDemuxFailed(TrackType aTrack, const MediaResult &aError);
void DoDemuxVideo();
void OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
void OnVideoDemuxFailed(const MediaResult& aError)
{
OnDemuxFailed(TrackType::kVideoTrack, aError);
}