author | JW Wang <jwwang@mozilla.com> |
Fri, 24 Jul 2015 09:03:11 +0800 | |
changeset 256218 | c0a6f89391ee20c27523d228a765ff22ce2b28fd |
parent 256217 | 684252f11061afa8532693516a14730f41b56841 |
child 256219 | 8caf2b95b2752e55d785fab9ff82c581db9b94f9 |
push id | 63268 |
push user | jwwang@mozilla.com |
push date | Wed, 05 Aug 2015 02:37:06 +0000 |
treeherder | mozilla-inbound@8caf2b95b275 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | kinetik |
bugs | 1187214 |
milestone | 42.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
dom/media/MediaEventSource.h | file | annotate | diff | comparison | revisions | |
dom/media/moz.build | file | annotate | diff | comparison | revisions |
new file mode 100644 --- /dev/null +++ b/dom/media/MediaEventSource.h @@ -0,0 +1,370 @@ +/* -*- 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/. */ + +#ifndef MediaEventSource_h_ +#define MediaEventSource_h_ + +#include "mozilla/AbstractThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/UniquePtr.h" + +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +namespace mozilla { + +/** + * A thread-safe tool to communicate "revocation" across threads. It is used to + * disconnect a listener from the event source to prevent future notifications + * from coming. Revoke() can be called on any thread. However, it is recommended + * to be called on the target thread to avoid race condition. + * + * RevocableToken is not exposed to the client code directly. + * Use MediaEventListener below to do the job. + */ +class RevocableToken { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RevocableToken); + +public: + RevocableToken() : mRevoked(false) {} + + void Revoke() { + mRevoked = true; + } + + bool IsRevoked() const { + return mRevoked; + } + +private: + ~RevocableToken() {} + Atomic<bool> mRevoked; +}; + +namespace detail { + +/** + * Define how an event type is passed internally in MediaEventSource and to the + * listeners. Specialized for the void type to pass a dummy bool instead. + */ +template <typename T> +struct EventTypeTraits { + typedef T ArgType; +}; + +template <> +struct EventTypeTraits<void> { + typedef bool ArgType; +}; + +/** + * Test if a method function or lambda accepts one or more arguments. + */ +template <typename T> +class TakeArgs { + template <typename C> static FalseType test(void(C::*)(), int); + template <typename C> static FalseType test(void(C::*)() const, int); + template <typename C> static FalseType test(void(C::*)() volatile, int); + template <typename C> static FalseType test(void(C::*)() const volatile, int); + template <typename F> static FalseType test(F&&, decltype(DeclVal<F>()(), 0)); + static TrueType test(...); +public: + typedef decltype(test(DeclVal<T>(), 0)) Type; +}; + +template <typename T> struct EventTarget; + +template <> +struct EventTarget<nsIEventTarget> { + static void + Dispatch(nsIEventTarget* aTarget, already_AddRefed<nsIRunnable>&& aTask) { + aTarget->Dispatch(Move(aTask), NS_DISPATCH_NORMAL); + } +}; + +template <> +struct EventTarget<AbstractThread> { + static void + Dispatch(AbstractThread* aTarget, already_AddRefed<nsIRunnable>&& aTask) { + aTarget->Dispatch(Move(aTask)); + } +}; + +/** + * Encapsulate a raw pointer to be captured by a lambda without causing + * static-analysis errors. + */ +template <typename T> +class RawPtr { +public: + explicit RawPtr(T* aPtr) : mPtr(aPtr) {} + T* get() const { return mPtr; } +private: + T* const mPtr; +}; + +} // namespace detail + +template <typename T> class MediaEventSource; + +/** + * Not thread-safe since this is not meant to be shared and therefore only + * move constructor is provided. Used to hold the result of + * MediaEventSource<T>::Connect() and call Disconnect() to disconnect the + * listener from an event source. + */ +class MediaEventListener { + template <typename T> + friend class MediaEventSource; + +public: + MediaEventListener() {} + + MediaEventListener(MediaEventListener&& aOther) + : mToken(Move(aOther.mToken)) {} + + MediaEventListener& operator=(MediaEventListener&& aOther) { + MOZ_ASSERT(!mToken, "Must disconnect the listener."); + mToken = Move(aOther.mToken); + return *this; + } + + ~MediaEventListener() { + MOZ_ASSERT(!mToken, "Must disconnect the listener."); + } + + void Disconnect() { + mToken->Revoke(); + mToken = nullptr; + } + +private: + // Avoid exposing RevocableToken directly to the client code so that + // listeners can be disconnected in a controlled manner. + explicit MediaEventListener(RevocableToken* aToken) : mToken(aToken) {} + nsRefPtr<RevocableToken> mToken; +}; + +/** + * A generic and thread-safe class to implement the observer pattern. + */ +template <typename EventType> +class MediaEventSource { + static_assert(!IsReference<EventType>::value, "Ref-type not supported!"); + typedef typename detail::EventTypeTraits<EventType>::ArgType ArgType; + + /** + * Stored by MediaEventSource to send notifications to the listener. + */ + class Listener { + public: + Listener() : mToken(new RevocableToken()) {} + + virtual ~Listener() { + MOZ_ASSERT(Token()->IsRevoked(), "Must disconnect the listener."); + } + + virtual void Dispatch(const ArgType& aEvent) = 0; + + RevocableToken* Token() const { + return mToken; + } + + private: + const nsRefPtr<RevocableToken> mToken; + }; + + /** + * Store the registered target thread and function so it knows where and to + * whom to send the event data. + */ + template<typename Target, typename Function> + class ListenerImpl : public Listener { + public: + explicit ListenerImpl(Target* aTarget, const Function& aFunction) + : mTarget(aTarget), mFunction(aFunction) {} + + // |Function| takes one argument. + void Dispatch(const ArgType& aEvent, TrueType) { + // Define our custom runnable to minimize copy of the event data. + // NS_NewRunnableFunction will result in 2 copies of the event data. + // One is captured by the lambda and the other is the copy of the lambda. + class R : public nsRunnable { + public: + R(RevocableToken* aToken, + const Function& aFunction, const ArgType& aEvent) + : mToken(aToken), mFunction(aFunction), mEvent(aEvent) {} + + NS_IMETHOD Run() override { + // Don't call the listener if it is disconnected. + if (!mToken->IsRevoked()) { + // Enable move whenever possible since mEvent won't be used anymore. + mFunction(Move(mEvent)); + } + return NS_OK; + } + + private: + nsRefPtr<RevocableToken> mToken; + Function mFunction; + ArgType mEvent; + }; + + nsCOMPtr<nsIRunnable> r = new R(this->Token(), mFunction, aEvent); + detail::EventTarget<Target>::Dispatch(mTarget.get(), r.forget()); + } + + // |Function| takes no arguments. Don't bother passing aEvent. + void Dispatch(const ArgType& aEvent, FalseType) { + nsRefPtr<RevocableToken> token = this->Token(); + const Function& function = mFunction; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () { + // Don't call the listener if it is disconnected. + if (!token->IsRevoked()) { + function(); + } + }); + detail::EventTarget<Target>::Dispatch(mTarget.get(), r.forget()); + } + + void Dispatch(const ArgType& aEvent) override { + Dispatch(aEvent, typename detail::TakeArgs<Function>::Type()); + } + + private: + const nsRefPtr<Target> mTarget; + Function mFunction; + }; + + template<typename Target, typename Function> + MediaEventListener + ConnectInternal(Target* aTarget, const Function& aFunction) { + MutexAutoLock lock(mMutex); + auto l = mListeners.AppendElement(); + l->reset(new ListenerImpl<Target, Function>(aTarget, aFunction)); + return MediaEventListener((*l)->Token()); + } + + // |Method| takes one argument. + template <typename Target, typename This, typename Method> + MediaEventListener + ConnectInternal(Target* aTarget, This* aThis, Method aMethod, TrueType) { + detail::RawPtr<This> thiz(aThis); + auto f = [=] (ArgType&& aEvent) { + (thiz.get()->*aMethod)(Move(aEvent)); + }; + return ConnectInternal(aTarget, f); + } + + // |Method| takes no arguments. Don't bother passing the event data. + template <typename Target, typename This, typename Method> + MediaEventListener + ConnectInternal(Target* aTarget, This* aThis, Method aMethod, FalseType) { + detail::RawPtr<This> thiz(aThis); + auto f = [=] () { + (thiz.get()->*aMethod)(); + }; + return ConnectInternal(aTarget, f); + } + +public: + /** + * Register a function to receive notifications from the event source. + * + * @param aTarget The target thread on which the function will run. + * @param aFunction A function to be called on the target thread. The function + * parameter must be convertible from |EventType|. + * @return An object used to disconnect from the event source. + */ + template<typename Function> + MediaEventListener + Connect(AbstractThread* aTarget, const Function& aFunction) { + return ConnectInternal(aTarget, aFunction); + } + + template<typename Function> + MediaEventListener + Connect(nsIEventTarget* aTarget, const Function& aFunction) { + return ConnectInternal(aTarget, aFunction); + } + + /** + * As above. + * + * Note we deliberately keep a weak reference to |aThis| in order not to + * change its lifetime. This is because notifications are dispatched + * asynchronously and removing a listener doesn't always break the reference + * cycle for the pending event could still hold a reference to |aThis|. + * + * The caller must call MediaEventListener::Disconnect() to avoid dangling + * pointers. + */ + template <typename This, typename Method> + MediaEventListener + Connect(AbstractThread* aTarget, This* aThis, Method aMethod) { + return ConnectInternal(aTarget, aThis, aMethod, + typename detail::TakeArgs<Method>::Type()); + } + + template <typename This, typename Method> + MediaEventListener + Connect(nsIEventTarget* aTarget, This* aThis, Method aMethod) { + return ConnectInternal(aTarget, aThis, aMethod, + typename detail::TakeArgs<Method>::Type()); + } + +protected: + MediaEventSource() : mMutex("MediaEventSource::mMutex") {} + + void NotifyInternal(const ArgType& aEvent) { + MutexAutoLock lock(mMutex); + for (int32_t i = mListeners.Length() - 1; i >= 0; --i) { + auto&& l = mListeners[i]; + // Remove disconnected listeners. + // It is not optimal but is simple and works well. + if (l->Token()->IsRevoked()) { + mListeners.RemoveElementAt(i); + continue; + } + l->Dispatch(aEvent); + } + } + +private: + Mutex mMutex; + nsTArray<UniquePtr<Listener>> mListeners; +}; + +/** + * A class to separate the interface of event subject (MediaEventSource) + * and event publisher. Mostly used as a member variable to publish events + * to the listeners. + */ +template <typename EventType> +class MediaEventProducer : public MediaEventSource<EventType> { +public: + void Notify(const EventType& aEvent) { + this->NotifyInternal(aEvent); + } +}; + +/** + * Specialization for void type. A dummy bool is passed to NotifyInternal + * since there is no way to pass a void value. + */ +template <> +class MediaEventProducer<void> : public MediaEventSource<void> { +public: + void Notify() { + this->NotifyInternal(true /* dummy */); + } +}; + +} // namespace mozilla + +#endif //MediaEventSource_h_
--- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -114,16 +114,17 @@ EXPORTS += [ 'Latency.h', 'MediaCache.h', 'MediaData.h', 'MediaDataDemuxer.h', 'MediaDecoder.h', 'MediaDecoderOwner.h', 'MediaDecoderReader.h', 'MediaDecoderStateMachine.h', + 'MediaEventSource.h', 'MediaFormatReader.h', 'MediaInfo.h', 'MediaMetadataManager.h', 'MediaQueue.h', 'MediaRecorder.h', 'MediaResource.h', 'MediaSegment.h', 'MediaStreamGraph.h',