mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1135424 - Implement MediaTimer. r=mattwoodrow
This commit is contained in:
parent
2eede60044
commit
b798727058
165
dom/media/MediaTimer.cpp
Normal file
165
dom/media/MediaTimer.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
/* -*- 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 "MediaTimer.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ADDREF(MediaTimer)
|
||||
NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy())
|
||||
|
||||
MediaTimer::MediaTimer()
|
||||
: mMonitor("MediaTimer Monitor")
|
||||
, mTimer(do_CreateInstance("@mozilla.org/timer;1"))
|
||||
, mUpdateScheduled(false)
|
||||
{
|
||||
// Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
|
||||
// thread, which is equivalent to an nsIThread for our purposes.
|
||||
RefPtr<SharedThreadPool> threadPool(
|
||||
SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1));
|
||||
mThread = threadPool.get();
|
||||
mTimer->SetTarget(mThread);
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::DispatchDestroy()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> task = NS_NewNonOwningRunnableMethod(this, &MediaTimer::Destroy);
|
||||
nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
|
||||
(void) rv;
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::Destroy()
|
||||
{
|
||||
MOZ_ASSERT(OnMediaTimerThread());
|
||||
|
||||
// Reject any outstanding entries. There's no need to acquire the monitor
|
||||
// here, because we're on the timer thread and all other references to us
|
||||
// must be gone.
|
||||
while (!mEntries.empty()) {
|
||||
mEntries.top().mPromise->Reject(false, __func__);
|
||||
mEntries.pop();
|
||||
}
|
||||
|
||||
// Cancel the timer if necessary.
|
||||
CancelTimerIfArmed();
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaTimer::OnMediaTimerThread()
|
||||
{
|
||||
bool rv = false;
|
||||
mThread->IsOnCurrentThread(&rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaTimerPromise>
|
||||
MediaTimer::WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite)
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
Entry e(aTimeStamp, aCallSite);
|
||||
nsRefPtr<MediaTimerPromise> p = e.mPromise.get();
|
||||
mEntries.push(e);
|
||||
ScheduleUpdate();
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::ScheduleUpdate()
|
||||
{
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
if (mUpdateScheduled) {
|
||||
return;
|
||||
}
|
||||
mUpdateScheduled = true;
|
||||
|
||||
nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(this, &MediaTimer::Update);
|
||||
nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL);
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
|
||||
(void) rv;
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::Update()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
UpdateLocked();
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::UpdateLocked()
|
||||
{
|
||||
MOZ_ASSERT(OnMediaTimerThread());
|
||||
mMonitor.AssertCurrentThreadOwns();
|
||||
mUpdateScheduled = false;
|
||||
|
||||
// Resolve all the promises whose time is up.
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
while (!mEntries.empty() && mEntries.top().mTimeStamp <= now) {
|
||||
mEntries.top().mPromise->Resolve(true, __func__);
|
||||
mEntries.pop();
|
||||
}
|
||||
|
||||
// If we've got no more entries, cancel any pending timer and bail out.
|
||||
if (mEntries.empty()) {
|
||||
CancelTimerIfArmed();
|
||||
return;
|
||||
}
|
||||
|
||||
// We've got more entries - (re)arm the timer for the soonest one.
|
||||
if (!TimerIsArmed() || mEntries.top().mTimeStamp < mCurrentTimerTarget) {
|
||||
CancelTimerIfArmed();
|
||||
ArmTimer(mEntries.top().mTimeStamp, now);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We use a callback function, rather than a callback method, to ensure that
|
||||
* the nsITimer does not artifically keep the refcount of the MediaTimer above
|
||||
* zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so that
|
||||
* we never fire against a dangling closure.
|
||||
*/
|
||||
|
||||
/* static */ void
|
||||
MediaTimer::TimerCallback(nsITimer* aTimer, void* aClosure)
|
||||
{
|
||||
static_cast<MediaTimer*>(aClosure)->TimerFired();
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::TimerFired()
|
||||
{
|
||||
MonitorAutoLock mon(mMonitor);
|
||||
MOZ_ASSERT(OnMediaTimerThread());
|
||||
mCurrentTimerTarget = TimeStamp();
|
||||
UpdateLocked();
|
||||
}
|
||||
|
||||
void
|
||||
MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow)
|
||||
{
|
||||
MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
|
||||
MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow);
|
||||
|
||||
// XPCOM timer resolution is in milliseconds. It's important to never resolve
|
||||
// a timer when mTarget might compare < now (even if very close), so round up.
|
||||
unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds());
|
||||
mCurrentTimerTarget = aTarget;
|
||||
nsresult rv = mTimer->InitWithFuncCallback(&TimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT);
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
|
||||
(void) rv;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
99
dom/media/MediaTimer.h
Normal file
99
dom/media/MediaTimer.h
Normal file
@ -0,0 +1,99 @@
|
||||
/* -*- 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(MediaTimer_h_)
|
||||
#define MediaTimer_h_
|
||||
|
||||
#include "MediaPromise.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "nsITimer.h"
|
||||
#include "nsRefPtr.h"
|
||||
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// This promise type is only exclusive because so far there isn't a reason for
|
||||
// it not to be. Feel free to change that.
|
||||
typedef MediaPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise;
|
||||
|
||||
// Timers only know how to fire at a given thread, which creates an impedence
|
||||
// mismatch with code that operates with MediaTaskQueues. This class solves
|
||||
// that mismatch with a dedicated (but shared) thread and a nice MediaPromise-y
|
||||
// interface.
|
||||
class MediaTimer
|
||||
{
|
||||
public:
|
||||
MediaTimer();
|
||||
|
||||
// We use a release with a custom Destroy().
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void);
|
||||
|
||||
nsRefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite);
|
||||
|
||||
private:
|
||||
virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); }
|
||||
|
||||
void DispatchDestroy(); // Invoked by Release on an arbitrary thread.
|
||||
void Destroy(); // Runs on the timer thread.
|
||||
|
||||
bool OnMediaTimerThread();
|
||||
void ScheduleUpdate();
|
||||
void Update();
|
||||
void UpdateLocked();
|
||||
|
||||
static void TimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
void TimerFired();
|
||||
void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow);
|
||||
|
||||
bool TimerIsArmed()
|
||||
{
|
||||
return !mCurrentTimerTarget.IsNull();
|
||||
}
|
||||
|
||||
void CancelTimerIfArmed()
|
||||
{
|
||||
MOZ_ASSERT(OnMediaTimerThread());
|
||||
if (TimerIsArmed()) {
|
||||
mTimer->Cancel();
|
||||
mCurrentTimerTarget = TimeStamp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Entry
|
||||
{
|
||||
TimeStamp mTimeStamp;
|
||||
nsRefPtr<MediaTimerPromise::Private> mPromise;
|
||||
|
||||
explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite)
|
||||
: mTimeStamp(aTimeStamp)
|
||||
, mPromise(new MediaTimerPromise::Private(aCallSite))
|
||||
{}
|
||||
|
||||
bool operator<(const Entry& aOther) const
|
||||
{
|
||||
return mTimeStamp < aOther.mTimeStamp;
|
||||
}
|
||||
};
|
||||
|
||||
ThreadSafeAutoRefCnt mRefCnt;
|
||||
NS_DECL_OWNINGTHREAD
|
||||
nsCOMPtr<nsIEventTarget> mThread;
|
||||
std::priority_queue<Entry> mEntries;
|
||||
Monitor mMonitor;
|
||||
nsCOMPtr<nsITimer> mTimer;
|
||||
TimeStamp mCurrentTimerTarget;
|
||||
bool mUpdateScheduled;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
@ -192,6 +192,7 @@ UNIFIED_SOURCES += [
|
||||
'MediaStreamGraph.cpp',
|
||||
'MediaStreamTrack.cpp',
|
||||
'MediaTaskQueue.cpp',
|
||||
'MediaTimer.cpp',
|
||||
'MediaTrack.cpp',
|
||||
'MediaTrackList.cpp',
|
||||
'MP3FrameParser.cpp',
|
||||
|
Loading…
Reference in New Issue
Block a user