2021-02-11 23:19:23 +03:00
|
|
|
/* 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 mozilla_media_mediaelementeventrunners_h
|
|
|
|
#define mozilla_media_mediaelementeventrunners_h
|
|
|
|
|
|
|
|
#include "mozilla/dom/PlayPromise.h"
|
|
|
|
#include "nsCycleCollectionParticipant.h"
|
|
|
|
#include "nsIContent.h"
|
|
|
|
#include "nsINamed.h"
|
|
|
|
#include "nsIRunnable.h"
|
|
|
|
#include "nsString.h"
|
|
|
|
#include "nsISupportsImpl.h"
|
|
|
|
#include "nsTString.h"
|
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
|
|
|
|
class HTMLMediaElement;
|
|
|
|
|
|
|
|
// Under certain conditions there may be no-one holding references to
|
|
|
|
// a media element from script, DOM parent, etc, but the element may still
|
|
|
|
// fire meaningful events in the future so we can't destroy it yet:
|
|
|
|
// 1) If the element is delaying the load event (or would be, if it were
|
|
|
|
// in a document), then events up to loadeddata or error could be fired,
|
|
|
|
// so we need to stay alive.
|
|
|
|
// 2) If the element is not paused and playback has not ended, then
|
|
|
|
// we will (or might) play, sending timeupdate and ended events and possibly
|
|
|
|
// audio output, so we need to stay alive.
|
|
|
|
// 3) if the element is seeking then we will fire seeking events and possibly
|
|
|
|
// start playing afterward, so we need to stay alive.
|
|
|
|
// 4) If autoplay could start playback in this element (if we got enough data),
|
|
|
|
// then we need to stay alive.
|
|
|
|
// 5) if the element is currently loading, not suspended, and its source is
|
|
|
|
// not a MediaSource, then script might be waiting for progress events or a
|
|
|
|
// 'stalled' or 'suspend' event, so we need to stay alive.
|
|
|
|
// If we're already suspended then (all other conditions being met),
|
|
|
|
// it's OK to just disappear without firing any more events,
|
|
|
|
// since we have the freedom to remain suspended indefinitely. Note
|
|
|
|
// that we could use this 'suspended' loophole to garbage-collect a suspended
|
|
|
|
// element in case 4 even if it had 'autoplay' set, but we choose not to.
|
|
|
|
// If someone throws away all references to a loading 'autoplay' element
|
|
|
|
// sound should still eventually play.
|
|
|
|
// 6) If the source is a MediaSource, most loading events will not fire unless
|
|
|
|
// appendBuffer() is called on a SourceBuffer, in which case something is
|
|
|
|
// already referencing the SourceBuffer, which keeps the associated media
|
|
|
|
// element alive. Further, a MediaSource will never time out the resource
|
|
|
|
// fetch, and so should not keep the media element alive if it is
|
|
|
|
// unreferenced. A pending 'stalled' event keeps the media element alive.
|
|
|
|
//
|
|
|
|
// Media elements owned by inactive documents (i.e. documents not contained in
|
|
|
|
// any document viewer) should never hold a self-reference because none of the
|
|
|
|
// above conditions are allowed: the element will stop loading and playing
|
|
|
|
// and never resume loading or playing unless its owner document changes to
|
|
|
|
// an active document (which can only happen if there is an external reference
|
|
|
|
// to the element).
|
|
|
|
// Media elements with no owner doc should be able to hold a self-reference.
|
|
|
|
// Something native must have created the element and may expect it to
|
|
|
|
// stay alive to play.
|
|
|
|
|
|
|
|
// It's very important that any change in state which could change the value of
|
|
|
|
// needSelfReference in AddRemoveSelfReference be followed by a call to
|
|
|
|
// AddRemoveSelfReference before this element could die!
|
|
|
|
// It's especially important if needSelfReference would change to 'true',
|
|
|
|
// since if we neglect to add a self-reference, this element might be
|
|
|
|
// garbage collected while there are still event listeners that should
|
|
|
|
// receive events. If we neglect to remove the self-reference then the element
|
|
|
|
// just lives longer than it needs to.
|
|
|
|
|
|
|
|
class nsMediaEventRunner : public nsIRunnable, public nsINamed {
|
|
|
|
public:
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsMediaEventRunner, nsIRunnable)
|
|
|
|
|
|
|
|
explicit nsMediaEventRunner(const nsAString& aName,
|
|
|
|
HTMLMediaElement* aElement,
|
|
|
|
const nsAString& aEventName = u"unknown"_ns);
|
|
|
|
|
|
|
|
void Cancel() { mElement = nullptr; }
|
|
|
|
NS_IMETHODIMP GetName(nsACString& aName) override {
|
|
|
|
aName = NS_ConvertUTF16toUTF8(mName).get();
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
nsString Name() const { return mName; }
|
|
|
|
nsString EventName() const { return mEventName; }
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual ~nsMediaEventRunner() = default;
|
|
|
|
bool IsCancelled();
|
2021-02-11 23:19:24 +03:00
|
|
|
nsresult DispatchEvent(const nsAString& aName);
|
2021-02-11 23:19:23 +03:00
|
|
|
|
|
|
|
RefPtr<HTMLMediaElement> mElement;
|
|
|
|
nsString mName;
|
|
|
|
nsString mEventName;
|
|
|
|
uint32_t mLoadID;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This runner is used to dispatch async event on media element.
|
|
|
|
*/
|
|
|
|
class nsAsyncEventRunner : public nsMediaEventRunner {
|
|
|
|
public:
|
|
|
|
nsAsyncEventRunner(const nsAString& aEventName, HTMLMediaElement* aElement)
|
|
|
|
: nsMediaEventRunner(u"nsAsyncEventRunner"_ns, aElement, aEventName) {}
|
|
|
|
NS_IMETHOD Run() override;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* These runners are used to handle `playing` event and address play promise.
|
|
|
|
*
|
|
|
|
* If no error is passed while constructing an instance, the instance will
|
|
|
|
* resolve the passed promises with undefined; otherwise, the instance will
|
|
|
|
* reject the passed promises with the passed error.
|
|
|
|
*
|
|
|
|
* The constructor appends the constructed instance into the passed media
|
|
|
|
* element's mPendingPlayPromisesRunners member and once the the runner is run
|
|
|
|
* (whether fulfilled or canceled), it removes itself from
|
|
|
|
* mPendingPlayPromisesRunners.
|
|
|
|
*/
|
|
|
|
class nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEventRunner {
|
|
|
|
public:
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
|
|
|
|
nsResolveOrRejectPendingPlayPromisesRunner, nsMediaEventRunner)
|
|
|
|
|
|
|
|
nsResolveOrRejectPendingPlayPromisesRunner(
|
|
|
|
HTMLMediaElement* aElement, nsTArray<RefPtr<PlayPromise>>&& aPromises,
|
|
|
|
nsresult aError = NS_OK);
|
|
|
|
void ResolveOrReject();
|
|
|
|
NS_IMETHOD Run() override;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual ~nsResolveOrRejectPendingPlayPromisesRunner() = default;
|
|
|
|
|
|
|
|
private:
|
|
|
|
nsTArray<RefPtr<PlayPromise>> mPromises;
|
|
|
|
nsresult mError;
|
|
|
|
};
|
|
|
|
|
|
|
|
class nsNotifyAboutPlayingRunner
|
|
|
|
: public nsResolveOrRejectPendingPlayPromisesRunner {
|
|
|
|
public:
|
|
|
|
nsNotifyAboutPlayingRunner(
|
|
|
|
HTMLMediaElement* aElement,
|
|
|
|
nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
|
|
|
|
: nsResolveOrRejectPendingPlayPromisesRunner(
|
|
|
|
aElement, std::move(aPendingPlayPromises)) {}
|
|
|
|
NS_IMETHOD Run() override;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This runner is used to dispatch a source error event, which would happen when
|
|
|
|
* loading resource failed.
|
|
|
|
*/
|
|
|
|
class nsSourceErrorEventRunner : public nsMediaEventRunner {
|
|
|
|
public:
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSourceErrorEventRunner,
|
|
|
|
nsMediaEventRunner)
|
|
|
|
nsSourceErrorEventRunner(HTMLMediaElement* aElement, nsIContent* aSource)
|
|
|
|
: nsMediaEventRunner(u"nsSourceErrorEventRunner"_ns, aElement),
|
|
|
|
mSource(aSource) {}
|
|
|
|
NS_IMETHOD Run() override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
virtual ~nsSourceErrorEventRunner() = default;
|
|
|
|
nsCOMPtr<nsIContent> mSource;
|
|
|
|
};
|
|
|
|
|
2021-02-11 23:19:24 +03:00
|
|
|
/**
|
|
|
|
* This runner is used to dispatch `timeupdate` event and ensure we don't
|
|
|
|
* dispatch `timeupdate` more often than once per `TIMEUPDATE_MS` if that is not
|
|
|
|
* a mandatory event.
|
|
|
|
*/
|
|
|
|
class nsTimeupdateRunner : public nsMediaEventRunner {
|
|
|
|
public:
|
|
|
|
nsTimeupdateRunner(HTMLMediaElement* aElement, bool aIsMandatory)
|
|
|
|
: nsMediaEventRunner(u"nsTimeupdateRunner"_ns, aElement,
|
|
|
|
u"timeupdate"_ns),
|
|
|
|
mIsMandatory(aIsMandatory) {}
|
|
|
|
NS_IMETHOD Run() override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool ShouldDispatchTimeupdate() const;
|
|
|
|
bool mIsMandatory;
|
|
|
|
};
|
|
|
|
|
2021-02-11 23:19:23 +03:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|
|
|
|
|
|
|
|
#endif // mozilla_media_mediaelementeventrunners_h
|