diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index cc700f377904..342f21b4ecd5 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -461,7 +461,6 @@ MediaDecoder::MediaDecoder() : gMediaDecoderLog = PR_NewLogModule("MediaDecoder"); } #endif - mAudioChannel = AudioChannelService::GetDefaultAudioChannel(); } diff --git a/dom/media/MediaDecoderReader.cpp b/dom/media/MediaDecoderReader.cpp index 1de9b6f12580..44ca2d92278d 100644 --- a/dom/media/MediaDecoderReader.cpp +++ b/dom/media/MediaDecoderReader.cpp @@ -27,6 +27,16 @@ extern PRLogModuleInfo* gMediaDecoderLog; #define DECODER_LOG(x, ...) #endif +PRLogModuleInfo* gMediaPromiseLog; + +void +EnsureMediaPromiseLog() +{ + if (!gMediaPromiseLog) { + gMediaPromiseLog = PR_NewLogModule("MediaPromise"); + } +} + class VideoQueueMemoryFunctor : public nsDequeFunctor { public: VideoQueueMemoryFunctor() : mSize(0) {} @@ -69,6 +79,7 @@ MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder) , mShutdown(false) { MOZ_COUNT_CTOR(MediaDecoderReader); + EnsureMediaPromiseLog(); } MediaDecoderReader::~MediaDecoderReader() diff --git a/dom/media/MediaPromise.h b/dom/media/MediaPromise.h new file mode 100644 index 000000000000..f455d5c48b90 --- /dev/null +++ b/dom/media/MediaPromise.h @@ -0,0 +1,322 @@ +/* -*- 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/. */ + +#if !defined(MediaPromise_h_) +#define MediaPromise_h_ + +#include "prlog.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/Monitor.h" + +#include "MediaTaskQueue.h" +#include "nsIEventTarget.h" + +/* Polyfill __func__ on MSVC for consumers to pass to the MediaPromise API. */ +#ifdef _MSC_VER +#define __func__ __FUNCTION__ +#endif + +namespace mozilla { + +extern PRLogModuleInfo* gMediaPromiseLog; +void EnsureMediaPromiseLog(); + +#define PROMISE_LOG(x, ...) \ + MOZ_ASSERT(gMediaPromiseLog); \ + PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__)) + +/* + * A promise manages an asynchronous request that may or may not be able to be + * fulfilled immediately. When an API returns a promise, the consumer may attach + * callbacks to be invoked (asynchronously, on a specified thread) when the + * request is either completed (resolved) or cannot be completed (rejected). + * + * By default, resolve and reject callbacks are always invoked on the same thread + * where Then() was invoked. + */ +template class MediaPromiseHolder; +template +class MediaPromise +{ +public: + typedef ResolveValueT ResolveValueType; + typedef RejectValueT RejectValueType; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPromise) + MediaPromise(const char* aCreationSite) + : mCreationSite(aCreationSite) + , mMutex("MediaPromise Mutex") + { + MOZ_COUNT_CTOR(MediaPromise); + PROMISE_LOG("%s creating MediaPromise (%p)", mCreationSite, this); + } + +protected: + + /* + * A ThenValue tracks a single consumer waiting on the promise. When a consumer + * invokes promise->Then(...), a ThenValue is created. Once the Promise is + * resolved or rejected, a ThenValueRunnable is dispatched, which invokes the + * resolve/reject method and then deletes the ThenValue. + */ + class ThenValueBase + { + public: + class ThenValueRunnable : public nsRunnable + { + public: + ThenValueRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue) + : mThenValue(aThenValue) + { + MOZ_COUNT_CTOR(ThenValueRunnable); + mResolveValue.emplace(aResolveValue); + } + + ThenValueRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue) + : mThenValue(aThenValue) + { + MOZ_COUNT_CTOR(ThenValueRunnable); + mRejectValue.emplace(aRejectValue); + } + + ~ThenValueRunnable() + { + MOZ_COUNT_DTOR(ThenValueRunnable); + MOZ_ASSERT(!mThenValue); + } + + NS_IMETHODIMP Run() + { + PROMISE_LOG("ThenValueRunnable::Run() [this=%p]", this); + if (mResolveValue.isSome()) { + mThenValue->DoResolve(mResolveValue.ref()); + } else { + mThenValue->DoReject(mRejectValue.ref()); + } + + delete mThenValue; + mThenValue = nullptr; + return NS_OK; + } + + private: + ThenValueBase* mThenValue; + Maybe mResolveValue; + Maybe mRejectValue; + }; + + ThenValueBase(const char* aCallSite) : mCallSite(aCallSite) + { + MOZ_COUNT_CTOR(ThenValueBase); + } + + virtual void Dispatch(MediaPromise *aPromise) = 0; + + protected: + // This may only be deleted by ThenValueRunnable::Run. + virtual ~ThenValueBase() { MOZ_COUNT_DTOR(ThenValueBase); } + + virtual void DoResolve(ResolveValueType aResolveValue) = 0; + virtual void DoReject(RejectValueType aRejectValue) = 0; + + const char* mCallSite; + }; + + template + class ThenValue : public ThenValueBase + { + public: + ThenValue(TargetType* aResponseTarget, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, + const char* aCallSite) + : ThenValueBase(aCallSite) + , mResponseTarget(aResponseTarget) + , mThisVal(aThisVal) + , mResolveMethod(aResolveMethod) + , mRejectMethod(aRejectMethod) {} + + void Dispatch(MediaPromise *aPromise) MOZ_OVERRIDE + { + aPromise->mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!aPromise->IsPending()); + bool resolved = aPromise->mResolveValue.isSome(); + nsRefPtr runnable = + resolved ? new (typename ThenValueBase::ThenValueRunnable)(this, aPromise->mResolveValue.ref()) + : new (typename ThenValueBase::ThenValueRunnable)(this, aPromise->mRejectValue.ref()); + PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]", + resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite, + runnable.get(), aPromise, this); + DebugOnly rv = DoDispatch(mResponseTarget, runnable); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + protected: + virtual void DoResolve(ResolveValueType aResolveValue) + { + ((*mThisVal).*mResolveMethod)(aResolveValue); + } + + virtual void DoReject(RejectValueType aRejectValue) + { + ((*mThisVal).*mRejectMethod)(aRejectValue); + } + + static nsresult DoDispatch(MediaTaskQueue* aTaskQueue, nsIRunnable* aRunnable) + { + return aTaskQueue->ForceDispatch(aRunnable); + } + + static nsresult DoDispatch(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable) + { + return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL); + } + + virtual ~ThenValue() {} + + private: + nsRefPtr mResponseTarget; + nsRefPtr mThisVal; + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + }; +public: + + template + void Then(TargetType* aResponseTarget, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, + const char* aCallSite) + { + MutexAutoLock lock(mMutex); + ThenValueBase* thenValue = new ThenValue(aResponseTarget, aThisVal, + aResolveMethod, aRejectMethod, + aCallSite); + PROMISE_LOG("%s invoking Then() [this=%p, thenValue=%p, aThisVal=%p, isPending=%d]", + aCallSite, this, thenValue, aThisVal, (int) IsPending()); + if (!IsPending()) { + thenValue->Dispatch(this); + } else { + mThenValues.AppendElement(thenValue); + } + } + +private: + // Resolve and Reject may only be invoked via MediaPromiseHolder. + friend class MediaPromiseHolder>; + void Resolve(ResolveValueType aResolveValue, const char* aResolveSite) + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(IsPending()); + PROMISE_LOG("%s resolving MediaPromise (%p created at %s)", aResolveSite, this, mCreationSite); + mResolveValue.emplace(aResolveValue); + DispatchAll(); + } + + void Reject(RejectValueType aRejectValue, const char* aRejectSite) + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(IsPending()); + PROMISE_LOG("%s rejecting MediaPromise (%p created at %s)", aRejectSite, this, mCreationSite); + mRejectValue.emplace(aRejectValue); + DispatchAll(); + } + +protected: + bool IsPending() { return mResolveValue.isNothing() && mRejectValue.isNothing(); } + void DispatchAll() + { + mMutex.AssertCurrentThreadOwns(); + for (size_t i = 0; i < mThenValues.Length(); ++i) + mThenValues[i]->Dispatch(this); + mThenValues.Clear(); + } + + ~MediaPromise() + { + MOZ_COUNT_DTOR(MediaPromise); + PROMISE_LOG("MediaPromise::~MediaPromise [this=%p]", this); + MOZ_ASSERT(!IsPending()); + }; + + const char* mCreationSite; // For logging + Mutex mMutex; + Maybe mResolveValue; + Maybe mRejectValue; + nsTArray mThenValues; +}; + +/* + * Class to encapsulate a promise for a particular role. Use this as the member + * variable for a class whose method returns a promise. + */ +template +class MediaPromiseHolder +{ +public: + MediaPromiseHolder() + : mMonitor(nullptr) {} + + ~MediaPromiseHolder() { MOZ_ASSERT(!mPromise); } + + already_AddRefed Ensure(const char* aMethodName) { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + if (!mPromise) { + mPromise = new PromiseType(aMethodName); + } + nsRefPtr p = mPromise; + return p.forget(); + } + + // Provide a Monitor that should always be held when accessing this instance. + void SetMonitor(Monitor* aMonitor) { mMonitor = aMonitor; } + + bool IsEmpty() + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + return !mPromise; + } + + void Resolve(typename PromiseType::ResolveValueType aResolveValue, + const char* aMethodName) + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + MOZ_ASSERT(mPromise); + mPromise->Resolve(aResolveValue, aMethodName); + mPromise = nullptr; + } + + void Reject(typename PromiseType::RejectValueType aRejectValue, + const char* aMethodName) + { + if (mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + } + MOZ_ASSERT(mPromise); + mPromise->Reject(aRejectValue, aMethodName); + mPromise = nullptr; + } + +private: + Monitor* mMonitor; + nsRefPtr mPromise; +}; + +#undef PROMISE_LOG + +} // namespace mozilla + +#endif diff --git a/dom/media/moz.build b/dom/media/moz.build index 86ef4410a0fb..76047be095d6 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -105,6 +105,7 @@ EXPORTS += [ 'MediaDecoderStateMachine.h', 'MediaInfo.h', 'MediaMetadataManager.h', + 'MediaPromise.h', 'MediaQueue.h', 'MediaRecorder.h', 'MediaResource.h',