diff --git a/dom/media/MediaCallbackID.cpp b/dom/media/MediaCallbackID.cpp new file mode 100644 index 000000000000..d1d40f7b297c --- /dev/null +++ b/dom/media/MediaCallbackID.cpp @@ -0,0 +1,50 @@ +#include "MediaCallbackID.h" + +namespace mozilla { + +char const* CallbackID::INVALID_TAG = "INVALID_TAG"; +int32_t const CallbackID::INVALID_ID = -1; + +CallbackID::CallbackID() + : mTag(INVALID_TAG), mID(INVALID_ID) +{ +} + +CallbackID::CallbackID(char const* aTag, int32_t aID /* = 0*/) + : mTag(aTag), mID(aID) +{ +} + +CallbackID& +CallbackID::operator++() +{ + ++mID; + return *this; +} + +CallbackID +CallbackID::operator++(int) +{ + CallbackID ret = *this; + ++(*this); // call prefix++ + return ret; +} + +bool +CallbackID::operator==(const CallbackID& rhs) const +{ + return (strcmp(mTag, rhs.mTag) == 0) && (mID == rhs.mID); +} + +bool +CallbackID::operator!=(const CallbackID& rhs) const +{ + return !(*this == rhs); +} + +CallbackID::operator int() const +{ + return mID; +} + +} // namespace mozilla diff --git a/dom/media/MediaCallbackID.h b/dom/media/MediaCallbackID.h new file mode 100644 index 000000000000..62cdd2fcc1aa --- /dev/null +++ b/dom/media/MediaCallbackID.h @@ -0,0 +1,38 @@ +/* -*- 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 MediaCallbackID_h_ +#define MediaCallbackID_h_ + +namespace mozilla { + +struct CallbackID +{ + static char const* INVALID_TAG; + static int32_t const INVALID_ID; + + CallbackID(); + + explicit CallbackID(char const* aTag, int32_t aID = 0); + + CallbackID& operator++(); // prefix++ + + CallbackID operator++(int); // postfix++ + + bool operator==(const CallbackID& rhs) const; + + bool operator!=(const CallbackID& rhs) const; + + operator int() const; + +private: + char const* mTag; + int32_t mID; +}; + +} // namespace mozilla + +#endif // MediaCallbackID_h_ diff --git a/dom/media/MediaDecoderReaderWrapper.cpp b/dom/media/MediaDecoderReaderWrapper.cpp index 5866158bcc7e..4bed533f1cca 100644 --- a/dom/media/MediaDecoderReaderWrapper.cpp +++ b/dom/media/MediaDecoderReaderWrapper.cpp @@ -144,6 +144,8 @@ MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(bool aIsRealTime, : mForceZeroStartTime(aIsRealTime || aReader->ForceZeroStartTime()) , mOwnerThread(aOwnerThread) , mReader(aReader) + , mAudioCallbackID("AudioCallbackID") + , mVideoCallbackID("VideoCallbackID") {} MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper() @@ -178,11 +180,30 @@ MediaDecoderReaderWrapper::AwaitStartTime() return mStartTimeRendezvous->AwaitStartTime(); } -RefPtr +void +MediaDecoderReaderWrapper::CancelAudioCallback(CallbackID aID) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(aID == mAudioCallbackID); + ++mAudioCallbackID; + mRequestAudioDataCB = nullptr; +} + +void +MediaDecoderReaderWrapper::CancelVideoCallback(CallbackID aID) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(aID == mVideoCallbackID); + ++mVideoCallbackID; + mRequestVideoDataCB = nullptr; +} + +void MediaDecoderReaderWrapper::RequestAudioData() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_ASSERT(!mShutdown); + MOZ_ASSERT(mRequestAudioDataCB, "Request audio data without callback!"); auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, &MediaDecoderReader::RequestAudioData); @@ -194,18 +215,31 @@ MediaDecoderReaderWrapper::RequestAudioData() ->CompletionPromise(); } - return p->Then(mOwnerThread, __func__, this, - &MediaDecoderReaderWrapper::OnSampleDecoded, - &MediaDecoderReaderWrapper::OnNotDecoded) - ->CompletionPromise(); + RefPtr self = this; + mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__, + [self] (MediaData* aAudioSample) { + MOZ_ASSERT(self->mRequestAudioDataCB); + self->mAudioDataRequest.Complete(); + self->OnSampleDecoded(self->mRequestAudioDataCB.get(), aAudioSample, TimeStamp()); + }, + [self] (MediaDecoderReader::NotDecodedReason aReason) { + MOZ_ASSERT(self->mRequestAudioDataCB); + self->mAudioDataRequest.Complete(); + self->OnNotDecoded(self->mRequestAudioDataCB.get(), aReason); + })); } -RefPtr +void MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_ASSERT(!mShutdown); + MOZ_ASSERT(mRequestVideoDataCB, "Request video data without callback!"); + + // Time the video decode and send this value back to callbacks who accept + // a TimeStamp as its second parameter. + TimeStamp videoDecodeStartTime = TimeStamp::Now(); if (aTimeThreshold.ToMicroseconds() > 0 && mStartTimeRendezvous->HaveStartTime()) { @@ -223,10 +257,32 @@ MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe, ->CompletionPromise(); } - return p->Then(mOwnerThread, __func__, this, - &MediaDecoderReaderWrapper::OnSampleDecoded, - &MediaDecoderReaderWrapper::OnNotDecoded) - ->CompletionPromise(); + RefPtr self = this; + mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__, + [self, videoDecodeStartTime] (MediaData* aVideoSample) { + MOZ_ASSERT(self->mRequestVideoDataCB); + self->mVideoDataRequest.Complete(); + self->OnSampleDecoded(self->mRequestVideoDataCB.get(), aVideoSample, videoDecodeStartTime); + }, + [self] (MediaDecoderReader::NotDecodedReason aReason) { + MOZ_ASSERT(self->mRequestVideoDataCB); + self->mVideoDataRequest.Complete(); + self->OnNotDecoded(self->mRequestVideoDataCB.get(), aReason); + })); +} + +bool +MediaDecoderReaderWrapper::IsRequestingAudioData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mAudioDataRequest.Exists(); +} + +bool +MediaDecoderReaderWrapper::IsRequestingVidoeData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mVideoDataRequest.Exists(); } RefPtr @@ -277,6 +333,10 @@ void MediaDecoderReaderWrapper::ResetDecode() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + + mAudioDataRequest.DisconnectIfExists(); + mVideoDataRequest.DisconnectIfExists(); + nsCOMPtr r = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode); mReader->OwnerThread()->Dispatch(r.forget()); @@ -286,6 +346,11 @@ RefPtr MediaDecoderReaderWrapper::Shutdown() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestAudioDataCB); + MOZ_ASSERT(!mRequestVideoDataCB); + MOZ_ASSERT(!mAudioDataRequest.Exists()); + MOZ_ASSERT(!mVideoDataRequest.Exists()); + mShutdown = true; if (mStartTimeRendezvous) { mStartTimeRendezvous->Destroy(); @@ -323,12 +388,25 @@ MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder* aMetadata) } void -MediaDecoderReaderWrapper::OnSampleDecoded(MediaData* aSample) +MediaDecoderReaderWrapper::OnSampleDecoded(CallbackBase* aCallback, + MediaData* aSample, + TimeStamp aDecodeStartTime) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); - if (!mShutdown) { - aSample->AdjustForStartTime(StartTime().ToMicroseconds()); - } + MOZ_ASSERT(!mShutdown); + + aSample->AdjustForStartTime(StartTime().ToMicroseconds()); + aCallback->OnResolved(aSample, aDecodeStartTime); +} + +void +MediaDecoderReaderWrapper::OnNotDecoded(CallbackBase* aCallback, + MediaDecoderReader::NotDecodedReason aReason) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + + aCallback->OnRejected(aReason); } } // namespace mozilla diff --git a/dom/media/MediaDecoderReaderWrapper.h b/dom/media/MediaDecoderReaderWrapper.h index 02c47d22ff94..231591ebeed6 100644 --- a/dom/media/MediaDecoderReaderWrapper.h +++ b/dom/media/MediaDecoderReaderWrapper.h @@ -12,6 +12,7 @@ #include "nsISupportsImpl.h" #include "MediaDecoderReader.h" +#include "MediaCallbackID.h" namespace mozilla { @@ -33,6 +34,135 @@ class MediaDecoderReaderWrapper { typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper); + /* + * Type 1: void(MediaData*) + * void(RefPtr) + */ + template + class ArgType1CheckHelper { + template + static TrueType + test(void(C::*aMethod)(Ts...), + decltype((DeclVal().*aMethod)(DeclVal()), 0)); + + template + static TrueType + test(F&&, decltype(DeclVal()(DeclVal()), 0)); + + static FalseType test(...); + public: + typedef decltype(test(DeclVal(), 0)) Type; + }; + + template + struct ArgType1Check : public ArgType1CheckHelper::Type {}; + + /* + * Type 2: void(MediaData*, TimeStamp) + * void(RefPtr, TimeStamp) + * void(MediaData*, TimeStamp&) + * void(RefPtr, const TimeStamp&&) + */ + template + class ArgType2CheckHelper { + + template + static TrueType + test(void(C::*aMethod)(Ts...), + decltype((DeclVal().*aMethod)(DeclVal(), DeclVal()), 0)); + + template + static TrueType + test(F&&, decltype(DeclVal()(DeclVal(), DeclVal()), 0)); + + static FalseType test(...); + public: + typedef decltype(test(DeclVal(), 0)) Type; + }; + + template + struct ArgType2Check : public ArgType2CheckHelper::Type {}; + + struct CallbackBase + { + virtual ~CallbackBase() {} + virtual void OnResolved(MediaData*, TimeStamp) = 0; + virtual void OnRejected(MediaDecoderReader::NotDecodedReason) = 0; + }; + + template + struct MethodCallback : public CallbackBase + { + MethodCallback(ThisType* aThis, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod) + : mThis(aThis), mResolveMethod(aResolveMethod), mRejectMethod(aRejectMethod) + { + } + + template + typename EnableIf::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp) + { + (mThis->*mResolveMethod)(aSample); + } + + template + typename EnableIf::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp aDecodeStartTime) + { + (mThis->*mResolveMethod)(aSample, aDecodeStartTime); + } + + void OnResolved(MediaData* aSample, TimeStamp aDecodeStartTime) override + { + CallHelper(aSample, aDecodeStartTime); + } + + void OnRejected(MediaDecoderReader::NotDecodedReason aReason) override + { + (mThis->*mRejectMethod)(aReason); + } + + RefPtr mThis; + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + }; + + template + struct FunctionCallback : public CallbackBase + { + FunctionCallback(ResolveFunctionType&& aResolveFuntion, RejectFunctionType&& aRejectFunction) + : mResolveFuntion(Move(aResolveFuntion)), mRejectFunction(Move(aRejectFunction)) + { + } + + template + typename EnableIf::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp) + { + mResolveFuntion(aSample); + } + + template + typename EnableIf::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp aDecodeStartTime) + { + mResolveFuntion(aSample, aDecodeStartTime); + } + + void OnResolved(MediaData* aSample, TimeStamp aDecodeStartTime) override + { + CallHelper(aSample, aDecodeStartTime); + } + + void OnRejected(MediaDecoderReader::NotDecodedReason aReason) override + { + mRejectFunction(aReason); + } + + ResolveFunctionType mResolveFuntion; + RejectFunctionType mRejectFunction; + }; + public: MediaDecoderReaderWrapper(bool aIsRealTime, AbstractThread* aOwnerThread, @@ -41,9 +171,83 @@ public: media::TimeUnit StartTime() const; RefPtr ReadMetadata(); RefPtr AwaitStartTime(); - RefPtr RequestAudioData(); - RefPtr RequestVideoData(bool aSkipToNextKeyframe, - media::TimeUnit aTimeThreshold); + + template + CallbackID + SetAudioCallback(ThisType* aThisVal, + ResolveMethodType aResolveMethod, + RejectMethodType aRejectMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestAudioDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestAudioDataCB.reset( + new MethodCallback( + aThisVal, aResolveMethod, aRejectMethod)); + + return mAudioCallbackID; + } + + template + CallbackID + SetAudioCallback(ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestAudioDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestAudioDataCB.reset( + new FunctionCallback( + Move(aResolveFunction), Move(aRejectFunction))); + + return mAudioCallbackID; + } + + template + CallbackID + SetVideoCallback(ThisType* aThisVal, + ResolveMethodType aResolveMethod, + RejectMethodType aRejectMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestVideoDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestVideoDataCB.reset( + new MethodCallback( + aThisVal, aResolveMethod, aRejectMethod)); + + return mVideoCallbackID; + } + + template + CallbackID + SetVideoCallback(ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestVideoDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestVideoDataCB.reset( + new FunctionCallback( + Move(aResolveFunction), Move(aRejectFunction))); + + return mVideoCallbackID; + } + + void CancelAudioCallback(CallbackID aID); + void CancelVideoCallback(CallbackID aID); + + // NOTE: please set callbacks before requesting audio/video data! + void RequestAudioData(); + void RequestVideoData(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold); + + bool IsRequestingAudioData() const; + bool IsRequestingVidoeData() const; + RefPtr Seek(SeekTarget aTarget, media::TimeUnit aEndTime); RefPtr WaitForData(MediaData::Type aType); RefPtr UpdateBufferedWithPromise(); @@ -96,8 +300,10 @@ private: void OnMetadataRead(MetadataHolder* aMetadata); void OnMetadataNotRead() {} - void OnSampleDecoded(MediaData* aSample); - void OnNotDecoded() {} + void OnSampleDecoded(CallbackBase* aCallback, MediaData* aSample, + TimeStamp aVideoDecodeStartTime); + void OnNotDecoded(CallbackBase* aCallback, + MediaDecoderReader::NotDecodedReason aReason); const bool mForceZeroStartTime; const RefPtr mOwnerThread; @@ -105,6 +311,17 @@ private: bool mShutdown = false; RefPtr mStartTimeRendezvous; + + UniquePtr mRequestAudioDataCB; + UniquePtr mRequestVideoDataCB; + MozPromiseRequestHolder mAudioDataRequest; + MozPromiseRequestHolder mVideoDataRequest; + + /* + * These callback ids are used to prevent mis-canceling callback. + */ + CallbackID mAudioCallbackID; + CallbackID mVideoCallbackID; }; } // namespace mozilla diff --git a/dom/media/moz.build b/dom/media/moz.build index 8c8c6a883655..804e09f25873 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -108,6 +108,7 @@ EXPORTS += [ 'Intervals.h', 'Latency.h', 'MediaCache.h', + 'MediaCallbackID.h', 'MediaData.h', 'MediaDataDemuxer.h', 'MediaDecoder.h', @@ -218,6 +219,7 @@ UNIFIED_SOURCES += [ 'GraphDriver.cpp', 'Latency.cpp', 'MediaCache.cpp', + 'MediaCallbackID.cpp', 'MediaData.cpp', 'MediaDecoder.cpp', 'MediaDecoderReader.cpp', diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h index 4e7338629e7e..d25fbea6066f 100644 --- a/xpcom/threads/MozPromise.h +++ b/xpcom/threads/MozPromise.h @@ -892,7 +892,7 @@ public: } } - bool Exists() { return !!mRequest; } + bool Exists() const { return !!mRequest; } private: RefPtr mRequest;