Bug 1161913 - Part 2. Request canvas to push out its next drawn frame instead of pulling it. r=mt

This commit is contained in:
Andreas Pehrson 2015-09-17 12:36:57 +08:00
parent a60fa1e45c
commit a4967132ae
4 changed files with 432 additions and 252 deletions

View File

@ -10,6 +10,7 @@
#include "jsapi.h"
#include "jsfriendapi.h"
#include "Layers.h"
#include "MediaSegment.h"
#include "mozilla/Assertions.h"
#include "mozilla/Base64.h"
#include "mozilla/CheckedInt.h"
@ -35,6 +36,7 @@
#include "nsLayoutUtils.h"
#include "nsMathUtils.h"
#include "nsNetUtil.h"
#include "nsRefreshDriver.h"
#include "nsStreamUtils.h"
#include "ActiveLayerTracker.h"
#include "WebGL1Context.h"
@ -48,6 +50,136 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
namespace mozilla {
namespace dom {
class RequestedFrameRefreshObserver : public nsARefreshObserver
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestedFrameRefreshObserver, override)
public:
RequestedFrameRefreshObserver(HTMLCanvasElement* const aOwningElement,
nsRefreshDriver* aRefreshDriver)
: mRegistered(false),
mOwningElement(aOwningElement),
mRefreshDriver(aRefreshDriver)
{
MOZ_ASSERT(mOwningElement);
}
static already_AddRefed<DataSourceSurface>
CopySurface(const RefPtr<SourceSurface>& aSurface)
{
RefPtr<DataSourceSurface> data = aSurface->GetDataSurface();
if (!data) {
return nullptr;
}
DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
if (!read.IsMapped()) {
return nullptr;
}
RefPtr<DataSourceSurface> copy =
Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
data->GetFormat(),
read.GetStride());
if (!copy) {
return nullptr;
}
DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
if (!write.IsMapped()) {
return nullptr;
}
MOZ_ASSERT(read.GetStride() == write.GetStride());
MOZ_ASSERT(data->GetSize() == copy->GetSize());
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
memcpy(write.GetData(), read.GetData(),
write.GetStride() * copy->GetSize().height);
return copy.forget();
}
void WillRefresh(TimeStamp aTime) override
{
MOZ_ASSERT(NS_IsMainThread());
if (!mOwningElement) {
return;
}
if (mOwningElement->IsWriteOnly()) {
return;
}
if (mOwningElement->IsContextCleanForFrameCapture()) {
return;
}
if (!mOwningElement->IsFrameCaptureRequested()) {
return;
}
RefPtr<SourceSurface> snapshot = mOwningElement->GetSurfaceSnapshot(nullptr);
if (!snapshot) {
return;
}
RefPtr<DataSourceSurface> copy = CopySurface(snapshot);
mOwningElement->SetFrameCapture(copy.forget());
mOwningElement->MarkContextCleanForFrameCapture();
}
void DetachFromRefreshDriver()
{
MOZ_ASSERT(mOwningElement);
MOZ_ASSERT(mRefreshDriver);
Unregister();
mRefreshDriver = nullptr;
}
void Register()
{
if (mRegistered) {
return;
}
MOZ_ASSERT(mRefreshDriver);
if (mRefreshDriver) {
mRefreshDriver->AddRefreshObserver(this, Flush_Display);
mRegistered = true;
}
}
void Unregister()
{
if (!mRegistered) {
return;
}
MOZ_ASSERT(mRefreshDriver);
if (mRefreshDriver) {
mRefreshDriver->RemoveRefreshObserver(this, Flush_Display);
mRegistered = false;
}
}
private:
virtual ~RequestedFrameRefreshObserver()
{
MOZ_ASSERT(!mRefreshDriver);
MOZ_ASSERT(!mRegistered);
}
bool mRegistered;
HTMLCanvasElement* const mOwningElement;
RefPtr<nsRefreshDriver> mRefreshDriver;
};
// ---------------------------------------------------------------------------
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HTMLCanvasPrintState, mCanvas,
mContext, mCallback)
@ -116,6 +248,9 @@ HTMLCanvasElement::HTMLCanvasElement(already_AddRefed<mozilla::dom::NodeInfo>& a
HTMLCanvasElement::~HTMLCanvasElement()
{
ResetPrintCallback();
if (mRequestedFrameRefreshObserver) {
mRequestedFrameRefreshObserver->DetachFromRefreshDriver();
}
}
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement,
@ -419,14 +554,6 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
return nullptr;
}
if (mCurrentContextType != CanvasContextType::Canvas2D) {
WebGLContext* gl = static_cast<WebGLContext*>(mCurrentContext.get());
if (!gl->IsPreservingDrawingBuffer()) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
}
nsRefPtr<CanvasCaptureMediaStream> stream =
CanvasCaptureMediaStream::CreateSourceStream(window, this);
if (!stream) {
@ -443,6 +570,9 @@ HTMLCanvasElement::CaptureStream(const Optional<double>& aFrameRate,
aRv.Throw(rv);
return nullptr;
}
stream->CreateDOMTrack(videoTrackId, MediaSegment::VIDEO);
RegisterFrameCaptureListener(stream->FrameCaptureListener());
return stream.forget();
}
@ -1047,6 +1177,85 @@ HTMLCanvasElement::IsContextCleanForFrameCapture()
return mCurrentContext && mCurrentContext->IsContextCleanForFrameCapture();
}
void
HTMLCanvasElement::RegisterFrameCaptureListener(FrameCaptureListener* aListener)
{
WeakPtr<FrameCaptureListener> listener = aListener;
if (mRequestedFrameListeners.Contains(listener)) {
return;
}
mRequestedFrameListeners.AppendElement(listener);
if (!mRequestedFrameRefreshObserver) {
nsIDocument* doc = OwnerDoc();
MOZ_RELEASE_ASSERT(doc);
nsIPresShell* shell = doc->GetShell();
MOZ_RELEASE_ASSERT(shell);
nsPresContext* context = shell->GetPresContext();
MOZ_RELEASE_ASSERT(context);
context = context->GetRootPresContext();
MOZ_RELEASE_ASSERT(context);
nsRefreshDriver* driver = context->RefreshDriver();
MOZ_RELEASE_ASSERT(driver);
mRequestedFrameRefreshObserver =
new RequestedFrameRefreshObserver(this, driver);
}
mRequestedFrameRefreshObserver->Register();
}
bool
HTMLCanvasElement::IsFrameCaptureRequested() const
{
for (WeakPtr<FrameCaptureListener> listener : mRequestedFrameListeners) {
if (!listener) {
continue;
}
if (listener->FrameCaptureRequested()) {
return true;
}
}
return false;
}
void
HTMLCanvasElement::SetFrameCapture(already_AddRefed<SourceSurface> aSurface)
{
RefPtr<SourceSurface> surface = aSurface;
CairoImage::Data imageData;
imageData.mSize = surface->GetSize();
imageData.mSourceSurface = surface;
nsRefPtr<CairoImage> image = new CairoImage();
image->SetData(imageData);
// Loop backwards to allow removing elements in the loop.
for (int i = mRequestedFrameListeners.Length() - 1; i >= 0; --i) {
WeakPtr<FrameCaptureListener> listener = mRequestedFrameListeners[i];
if (!listener) {
// listener was destroyed. Remove it from the list.
mRequestedFrameListeners.RemoveElementAt(i);
continue;
}
nsRefPtr<Image> imageRefCopy = image.get();
listener->NewFrame(imageRefCopy.forget());
}
if (mRequestedFrameListeners.IsEmpty()) {
mRequestedFrameRefreshObserver->Unregister();
}
}
already_AddRefed<SourceSurface>
HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha)
{

View File

@ -7,6 +7,7 @@
#define mozilla_dom_HTMLCanvasElement_h
#include "mozilla/Attributes.h"
#include "mozilla/WeakPtr.h"
#include "nsIDOMHTMLCanvasElement.h"
#include "nsGenericHTMLElement.h"
#include "nsGkAtoms.h"
@ -22,6 +23,7 @@ namespace mozilla {
namespace layers {
class CanvasLayer;
class Image;
class LayerManager;
} // namespace layers
namespace gfx {
@ -34,6 +36,7 @@ class File;
class FileCallback;
class HTMLCanvasPrintState;
class PrintCallback;
class RequestedFrameRefreshObserver;
enum class CanvasContextType : uint8_t {
NoContext,
@ -42,6 +45,44 @@ enum class CanvasContextType : uint8_t {
WebGL2
};
/*
* FrameCaptureListener is used by captureStream() as a way of getting video
* frames from the canvas. On a refresh driver tick after something has been
* drawn to the canvas since the last such tick, all registered
* FrameCaptureListeners whose `mFrameCaptureRequested` equals `true`,
* will be given a copy of the just-painted canvas.
* All FrameCaptureListeners get the same copy.
*/
class FrameCaptureListener : public SupportsWeakPtr<FrameCaptureListener>
{
public:
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(FrameCaptureListener)
FrameCaptureListener()
: mFrameCaptureRequested(false) {}
/*
* Called when a frame capture is desired on next paint.
*/
void RequestFrameCapture() { mFrameCaptureRequested = true; }
/*
* Indicates to the canvas whether or not this listener has requested a frame.
*/
bool FrameCaptureRequested() const { return mFrameCaptureRequested; }
/*
* Interface through which new video frames will be provided while
* `mFrameCaptureRequested` is `true`.
*/
virtual void NewFrame(already_AddRefed<layers::Image> aImage) = 0;
protected:
virtual ~FrameCaptureListener() {}
bool mFrameCaptureRequested;
};
class HTMLCanvasElement final : public nsGenericHTMLElement,
public nsIDOMHTMLCanvasElement
{
@ -171,6 +212,29 @@ public:
virtual already_AddRefed<gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr);
/*
* Register a FrameCaptureListener with this canvas.
* The canvas hooks into the RefreshDriver while there are
* FrameCaptureListeners registered.
* The registered FrameCaptureListeners are stored as WeakPtrs, thus it's the
* caller's responsibility to keep them alive. Once a registered
* FrameCaptureListener is destroyed it will be automatically deregistered.
*/
void RegisterFrameCaptureListener(FrameCaptureListener* aListener);
/*
* Returns true when there is at least one registered FrameCaptureListener
* that has requested a frame capture.
*/
bool IsFrameCaptureRequested() const;
/*
* Called by the RefreshDriver hook when a frame has been captured.
* Makes a copy of the provided surface and hands it to all
* FrameCaptureListeners having requested frame capture.
*/
void SetFrameCapture(already_AddRefed<gfx::SourceSurface> aSurface);
virtual bool ParseAttribute(int32_t aNamespaceID,
nsIAtom* aAttribute,
const nsAString& aValue,
@ -253,6 +317,8 @@ protected:
nsRefPtr<PrintCallback> mPrintCallback;
nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
nsRefPtr<HTMLCanvasPrintState> mPrintState;
nsTArray<WeakPtr<FrameCaptureListener>> mRequestedFrameListeners;
nsRefPtr<RequestedFrameRefreshObserver> mRequestedFrameRefreshObserver;
public:
// Record whether this canvas should be write-only or not.

View File

@ -9,9 +9,8 @@
#include "ImageContainer.h"
#include "MediaStreamGraph.h"
#include "mozilla/dom/CanvasCaptureMediaStreamBinding.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Mutex.h"
#include "mozilla/Atomics.h"
#include "nsContentUtils.h"
using namespace mozilla::layers;
@ -24,31 +23,44 @@ class OutputStreamDriver::StreamListener : public MediaStreamListener
{
public:
explicit StreamListener(OutputStreamDriver* aDriver,
TrackID aTrackId,
SourceMediaStream* aSourceStream)
: mSourceStream(aSourceStream)
, mMutex("CanvasCaptureMediaStream::OSD::StreamListener")
, mDriver(aDriver)
: mEnded(false)
, mSourceStream(aSourceStream)
, mTrackId(aTrackId)
, mMutex("CanvasCaptureMediaStream OutputStreamDriver::StreamListener")
, mImage(nullptr)
{
MOZ_ASSERT(mDriver);
MOZ_ASSERT(mSourceStream);
}
void Forget() {
MOZ_ASSERT(NS_IsMainThread());
void EndStream() {
mEnded = true;
}
void SetImage(const nsRefPtr<layers::Image>& aImage)
{
MutexAutoLock lock(mMutex);
mDriver = nullptr;
mImage = aImage;
}
virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
{
// Called on the MediaStreamGraph thread.
StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
if (delta > 0) {
MutexAutoLock lock(mMutex);
MOZ_ASSERT(mSourceStream);
MutexAutoLock lock(mMutex);
if (mDriver) {
mDriver->NotifyPull(aDesiredTime);
} else {
// The DOM stream is dead, let's end it
nsRefPtr<Image> image = mImage;
IntSize size = image ? image->GetSize() : IntSize(0, 0);
VideoSegment segment;
segment.AppendFrame(image.forget(), delta, size);
mSourceStream->AppendToTrack(mTrackId, &segment);
}
if (mEnded) {
mSourceStream->EndAllTrackAndFinish();
}
}
@ -57,224 +69,62 @@ protected:
~StreamListener() { }
private:
nsRefPtr<SourceMediaStream> mSourceStream;
Atomic<bool> mEnded;
const nsRefPtr<SourceMediaStream> mSourceStream;
const TrackID mTrackId;
// The below members are protected by mMutex.
Mutex mMutex;
// This is a raw pointer to avoid a reference cycle with OutputStreamDriver.
// Accessed on main and MediaStreamGraph threads, set on main thread.
OutputStreamDriver* mDriver;
// The below members are protected by mMutex.
nsRefPtr<layers::Image> mImage;
};
OutputStreamDriver::OutputStreamDriver(CanvasCaptureMediaStream* aDOMStream,
OutputStreamDriver::OutputStreamDriver(SourceMediaStream* aSourceStream,
const TrackID& aTrackId)
: mDOMStream(aDOMStream)
, mSourceStream(nullptr)
, mStarted(false)
, mStreamListener(nullptr)
, mTrackId(aTrackId)
, mMutex("CanvasCaptureMediaStream::OutputStreamDriver")
, mImage(nullptr)
: FrameCaptureListener()
, mSourceStream(aSourceStream)
, mStreamListener(new StreamListener(this, aTrackId, aSourceStream))
{
MOZ_ASSERT(mDOMStream);
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mSourceStream);
mSourceStream->AddListener(mStreamListener);
mSourceStream->AddTrack(aTrackId, 0, new VideoSegment());
mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
mSourceStream->SetPullEnabled(true);
// All CanvasCaptureMediaStreams shall at least get one frame.
mFrameCaptureRequested = true;
}
OutputStreamDriver::~OutputStreamDriver()
{
MOZ_ASSERT(NS_IsMainThread());
if (mStreamListener) {
// MediaStreamGraph will keep the listener alive until it can finish the
// stream on the next NotifyPull().
mStreamListener->Forget();
mStreamListener->EndStream();
}
}
nsresult
OutputStreamDriver::Start()
{
if (mStarted) {
return NS_ERROR_ALREADY_INITIALIZED;
}
MOZ_ASSERT(mDOMStream);
mDOMStream->CreateDOMTrack(mTrackId, MediaSegment::VIDEO);
mSourceStream = mDOMStream->GetStream()->AsSourceStream();
MOZ_ASSERT(mSourceStream);
mStreamListener = new StreamListener(this, mSourceStream);
mSourceStream->AddListener(mStreamListener);
mSourceStream->AddTrack(mTrackId, 0, new VideoSegment());
mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
mSourceStream->SetPullEnabled(true);
// Run StartInternal() in stable state to allow it to directly capture a frame
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &OutputStreamDriver::StartInternal);
nsContentUtils::RunInStableState(runnable.forget());
mStarted = true;
return NS_OK;
}
void
OutputStreamDriver::ForgetDOMStream()
OutputStreamDriver::SetImage(const nsRefPtr<layers::Image>& aImage)
{
if (mStreamListener) {
mStreamListener->Forget();
mStreamListener->SetImage(aImage);
}
mDOMStream = nullptr;
}
void
OutputStreamDriver::AppendToTrack(StreamTime aDuration)
{
MOZ_ASSERT(mSourceStream);
MutexAutoLock lock(mMutex);
nsRefPtr<Image> image = mImage;
IntSize size = image ? image->GetSize() : IntSize(0, 0);
VideoSegment segment;
segment.AppendFrame(image.forget(), aDuration, size);
mSourceStream->AppendToTrack(mTrackId, &segment);
}
void
OutputStreamDriver::NotifyPull(StreamTime aDesiredTime)
{
StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
if (delta > 0) {
// nullptr images are allowed
AppendToTrack(delta);
}
}
void
OutputStreamDriver::SetImage(Image* aImage)
{
MutexAutoLock lock(mMutex);
mImage = aImage;
}
// ----------------------------------------------------------------------
class TimerDriver : public OutputStreamDriver
, public nsITimerCallback
{
public:
explicit TimerDriver(CanvasCaptureMediaStream* aDOMStream,
explicit TimerDriver(SourceMediaStream* aSourceStream,
const double& aFPS,
const TrackID& aTrackId)
: OutputStreamDriver(aDOMStream, aTrackId)
: OutputStreamDriver(aSourceStream, aTrackId)
, mFPS(aFPS)
, mTimer(nullptr)
{
}
void ForgetDOMStream() override
{
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
OutputStreamDriver::ForgetDOMStream();
}
nsresult
TakeSnapshot()
{
// mDOMStream can't be killed while we're on main thread
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(DOMStream());
if (!DOMStream()->Canvas()) {
// DOMStream's canvas pointer was garbage collected. We can abort now.
return NS_ERROR_NOT_AVAILABLE;
}
MOZ_ASSERT(DOMStream()->Canvas());
if (DOMStream()->Canvas()->IsWriteOnly()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// Pass `nullptr` to force alpha-premult.
RefPtr<SourceSurface> snapshot = DOMStream()->Canvas()->GetSurfaceSnapshot(nullptr);
if (!snapshot) {
return NS_ERROR_FAILURE;
}
RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
if (!data) {
return NS_ERROR_FAILURE;
}
RefPtr<DataSourceSurface> copy;
{
DataSourceSurface::ScopedMap read(data, DataSourceSurface::READ);
if (!read.IsMapped()) {
return NS_ERROR_FAILURE;
}
copy = Factory::CreateDataSourceSurfaceWithStride(data->GetSize(),
data->GetFormat(),
read.GetStride());
if (!copy) {
return NS_ERROR_FAILURE;
}
DataSourceSurface::ScopedMap write(copy, DataSourceSurface::WRITE);
if (!write.IsMapped()) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(read.GetStride() == write.GetStride());
MOZ_ASSERT(data->GetSize() == copy->GetSize());
MOZ_ASSERT(data->GetFormat() == copy->GetFormat());
memcpy(write.GetData(), read.GetData(),
write.GetStride() * copy->GetSize().height);
}
CairoImage::Data imageData;
imageData.mSize = copy->GetSize();
imageData.mSourceSurface = copy;
RefPtr<CairoImage> image = new layers::CairoImage();
image->SetData(imageData);
SetImage(image);
return NS_OK;
}
NS_IMETHODIMP
Notify(nsITimer* aTimer) override
{
nsresult rv = TakeSnapshot();
if (NS_FAILED(rv)) {
aTimer->Cancel();
}
return rv;
}
virtual void RequestFrame() override
{
TakeSnapshot();
}
NS_DECL_ISUPPORTS_INHERITED
protected:
virtual ~TimerDriver() {}
virtual void StartInternal() override
{
// Always capture at least one frame.
DebugOnly<nsresult> rv = TakeSnapshot();
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (mFPS == 0.0) {
return;
}
@ -283,18 +133,45 @@ protected:
if (!mTimer) {
return;
}
mTimer->InitWithCallback(this, int(1000 / mFPS), nsITimer::TYPE_REPEATING_SLACK);
mTimer->InitWithFuncCallback(&TimerTick, this, int(1000 / mFPS), nsITimer::TYPE_REPEATING_SLACK);
}
static void TimerTick(nsITimer* aTimer, void* aClosure)
{
MOZ_ASSERT(aClosure);
TimerDriver* driver = static_cast<TimerDriver*>(aClosure);
driver->RequestFrameCapture();
}
void NewFrame(already_AddRefed<Image> aImage) override
{
nsRefPtr<Image> image = aImage;
if (!mFrameCaptureRequested) {
return;
}
mFrameCaptureRequested = false;
SetImage(image.forget());
}
void Forget() override
{
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
}
}
protected:
virtual ~TimerDriver() {}
private:
const double mFPS;
nsCOMPtr<nsITimer> mTimer;
};
NS_IMPL_ADDREF_INHERITED(TimerDriver, OutputStreamDriver)
NS_IMPL_RELEASE_INHERITED(TimerDriver, OutputStreamDriver)
NS_IMPL_QUERY_INTERFACE(TimerDriver, nsITimerCallback)
// ----------------------------------------------------------------------
NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream, DOMMediaStream,
@ -315,7 +192,7 @@ CanvasCaptureMediaStream::CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas)
CanvasCaptureMediaStream::~CanvasCaptureMediaStream()
{
if (mOutputStreamDriver) {
mOutputStreamDriver->ForgetDOMStream();
mOutputStreamDriver->Forget();
}
}
@ -328,8 +205,9 @@ CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGive
void
CanvasCaptureMediaStream::RequestFrame()
{
MOZ_ASSERT(mOutputStreamDriver);
if (mOutputStreamDriver) {
mOutputStreamDriver->RequestFrame();
mOutputStreamDriver->RequestFrameCapture();
}
}
@ -340,15 +218,16 @@ CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
if (!aFPS.WasPassed()) {
// TODO (Bug 1152298): Implement a real AutoDriver.
// We use a 30FPS TimerDriver for now.
mOutputStreamDriver = new TimerDriver(this, 30.0, aTrackId);
mOutputStreamDriver = new TimerDriver(GetStream()->AsSourceStream(), 30.0, aTrackId);
} else if (aFPS.Value() < 0) {
return NS_ERROR_ILLEGAL_VALUE;
} else {
// Cap frame rate to 60 FPS for sanity
double fps = std::min(60.0, aFPS.Value());
mOutputStreamDriver = new TimerDriver(this, fps, aTrackId);
mOutputStreamDriver =
new TimerDriver(GetStream()->AsSourceStream(), fps, aTrackId);
}
return mOutputStreamDriver->Start();
return NS_OK;
}
already_AddRefed<CanvasCaptureMediaStream>
@ -363,6 +242,12 @@ CanvasCaptureMediaStream::CreateSourceStream(nsIDOMWindow* aWindow,
return stream.forget();
}
FrameCaptureListener*
CanvasCaptureMediaStream::FrameCaptureListener()
{
return mOutputStreamDriver;
}
} // namespace dom
} // namespace mozilla

View File

@ -7,6 +7,7 @@
#define mozilla_dom_CanvasCaptureMediaStream_h_
#include "DOMMediaStream.h"
#include "mozilla/dom/HTMLCanvasElement.h"
#include "StreamBuffer.h"
namespace mozilla {
@ -21,60 +22,78 @@ class Image;
namespace dom {
class CanvasCaptureMediaStream;
class HTMLCanvasElement;
class OutputStreamFrameListener;
class OutputStreamDriver
/*
* The CanvasCaptureMediaStream is a MediaStream subclass that provides a video
* track containing frames from a canvas. See an architectural overview below.
*
* ----------------------------------------------------------------------------
* === Main Thread === __________________________
* | |
* | CanvasCaptureMediaStream |
* |__________________________|
* |
* | RequestFrame()
* v
* ________________________
* ________ FrameCaptureRequested? | |
* | | ------------------------> | OutputStreamDriver |
* | Canvas | SetFrameCapture() | (FrameCaptureListener) |
* |________| ------------------------> |________________________|
* |
* | SetImage()
* v
* ___________________
* | StreamListener |
* ---------------------------------------| (All image access |----------------
* === MediaStreamGraph Thread === | Mutex Guarded) |
* |___________________|
* ^ |
* NotifyPull() | | AppendToTrack()
* | v
* ___________________________
* | |
* | MSG / SourceMediaStream |
* |___________________________|
* ----------------------------------------------------------------------------
*/
/*
* Base class for drivers of the output stream.
* It is up to each sub class to implement the NewFrame() callback of
* FrameCaptureListener.
*/
class OutputStreamDriver : public FrameCaptureListener
{
public:
OutputStreamDriver(CanvasCaptureMediaStream* aDOMStream,
OutputStreamDriver(SourceMediaStream* aSourceStream,
const TrackID& aTrackId);
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OutputStreamDriver);
nsresult Start();
virtual void ForgetDOMStream();
virtual void RequestFrame() { }
CanvasCaptureMediaStream* DOMStream() const { return mDOMStream; }
protected:
virtual ~OutputStreamDriver();
class StreamListener;
/*
* Appends mImage to video track for the desired duration.
*/
void AppendToTrack(StreamTime aDuration);
void NotifyPull(StreamTime aDesiredTime);
/*
* Sub classes can SetImage() to update the image being appended to the
* output stream. It will be appended on the next NotifyPull from MSG.
*/
void SetImage(layers::Image* aImage);
void SetImage(const nsRefPtr<layers::Image>& aImage);
/*
* Called in main thread stable state to initialize sub classes.
* Makes sure any internal resources this driver is holding that may create
* reference cycles are released.
*/
virtual void StartInternal() = 0;
virtual void Forget() {}
protected:
virtual ~OutputStreamDriver();
class StreamListener;
private:
// This is a raw pointer to avoid a reference cycle between OutputStreamDriver
// and CanvasCaptureMediaStream. ForgetDOMStream() will be called by
// ~CanvasCaptureMediaStream() to make sure we don't do anything illegal.
CanvasCaptureMediaStream* mDOMStream;
nsRefPtr<SourceMediaStream> mSourceStream;
bool mStarted;
nsRefPtr<StreamListener> mStreamListener;
const TrackID mTrackId;
// The below members are protected by mMutex.
Mutex mMutex;
nsRefPtr<layers::Image> mImage;
};
class CanvasCaptureMediaStream: public DOMMediaStream
class CanvasCaptureMediaStream : public DOMMediaStream
{
public:
explicit CanvasCaptureMediaStream(HTMLCanvasElement* aCanvas);
@ -89,6 +108,7 @@ public:
// WebIDL
HTMLCanvasElement* Canvas() const { return mCanvas; }
void RequestFrame();
dom::FrameCaptureListener* FrameCaptureListener();
/**
* Create a CanvasCaptureMediaStream whose underlying stream is a SourceMediaStream.