diff options
Diffstat (limited to 'dom/media/CanvasCaptureMediaStream.cpp')
-rw-r--r-- | dom/media/CanvasCaptureMediaStream.cpp | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/dom/media/CanvasCaptureMediaStream.cpp b/dom/media/CanvasCaptureMediaStream.cpp new file mode 100644 index 000000000..f40f01e21 --- /dev/null +++ b/dom/media/CanvasCaptureMediaStream.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 "CanvasCaptureMediaStream.h" +#include "DOMMediaStream.h" +#include "gfxPlatform.h" +#include "ImageContainer.h" +#include "MediaStreamGraph.h" +#include "mozilla/dom/CanvasCaptureMediaStreamBinding.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Atomics.h" +#include "nsContentUtils.h" + +using namespace mozilla::layers; +using namespace mozilla::gfx; + +namespace mozilla { +namespace dom { + +class OutputStreamDriver::StreamListener : public MediaStreamListener +{ +public: + explicit StreamListener(OutputStreamDriver* aDriver, + TrackID aTrackId, + PrincipalHandle aPrincipalHandle, + SourceMediaStream* aSourceStream) + : mEnded(false) + , mSourceStream(aSourceStream) + , mTrackId(aTrackId) + , mPrincipalHandle(aPrincipalHandle) + , mMutex("CanvasCaptureMediaStream OutputStreamDriver::StreamListener") + , mImage(nullptr) + { + MOZ_ASSERT(mSourceStream); + } + + void EndStream() { + mEnded = true; + } + + void SetImage(const RefPtr<layers::Image>& aImage) + { + MutexAutoLock lock(mMutex); + mImage = aImage; + } + + 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); + + RefPtr<Image> image = mImage; + IntSize size = image ? image->GetSize() : IntSize(0, 0); + VideoSegment segment; + segment.AppendFrame(image.forget(), delta, size, mPrincipalHandle); + + mSourceStream->AppendToTrack(mTrackId, &segment); + } + + if (mEnded) { + mSourceStream->EndAllTrackAndFinish(); + } + } + +protected: + ~StreamListener() { } + +private: + Atomic<bool> mEnded; + const RefPtr<SourceMediaStream> mSourceStream; + const TrackID mTrackId; + const PrincipalHandle mPrincipalHandle; + + Mutex mMutex; + // The below members are protected by mMutex. + RefPtr<layers::Image> mImage; +}; + +OutputStreamDriver::OutputStreamDriver(SourceMediaStream* aSourceStream, + const TrackID& aTrackId, + const PrincipalHandle& aPrincipalHandle) + : FrameCaptureListener() + , mSourceStream(aSourceStream) + , mStreamListener(new StreamListener(this, aTrackId, aPrincipalHandle, + aSourceStream)) +{ + 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->EndStream(); + } +} + +void +OutputStreamDriver::SetImage(const RefPtr<layers::Image>& aImage) +{ + if (mStreamListener) { + mStreamListener->SetImage(aImage); + } +} + +// ---------------------------------------------------------------------- + +class TimerDriver : public OutputStreamDriver +{ +public: + explicit TimerDriver(SourceMediaStream* aSourceStream, + const double& aFPS, + const TrackID& aTrackId, + const PrincipalHandle& aPrincipalHandle) + : OutputStreamDriver(aSourceStream, aTrackId, aPrincipalHandle) + , mFPS(aFPS) + , mTimer(nullptr) + { + if (mFPS == 0.0) { + return; + } + + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + if (!mTimer) { + return; + } + 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 + { + RefPtr<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; +}; + +// ---------------------------------------------------------------------- + +class AutoDriver : public OutputStreamDriver +{ +public: + explicit AutoDriver(SourceMediaStream* aSourceStream, + const TrackID& aTrackId, + const PrincipalHandle& aPrincipalHandle) + : OutputStreamDriver(aSourceStream, aTrackId, aPrincipalHandle) {} + + void NewFrame(already_AddRefed<Image> aImage) override + { + // Don't reset `mFrameCaptureRequested` since AutoDriver shall always have + // `mFrameCaptureRequested` set to true. + // This also means we should accept every frame as NewFrame is called only + // after something changed. + + RefPtr<Image> image = aImage; + SetImage(image.forget()); + } + +protected: + virtual ~AutoDriver() {} +}; + +// ---------------------------------------------------------------------- + +NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream, DOMMediaStream, + mCanvas) + +NS_IMPL_ADDREF_INHERITED(CanvasCaptureMediaStream, DOMMediaStream) +NS_IMPL_RELEASE_INHERITED(CanvasCaptureMediaStream, DOMMediaStream) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream) +NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream) + +CanvasCaptureMediaStream::CanvasCaptureMediaStream(nsPIDOMWindowInner* aWindow, + HTMLCanvasElement* aCanvas) + : DOMMediaStream(aWindow, nullptr) + , mCanvas(aCanvas) + , mOutputStreamDriver(nullptr) +{ +} + +CanvasCaptureMediaStream::~CanvasCaptureMediaStream() +{ + if (mOutputStreamDriver) { + mOutputStreamDriver->Forget(); + } +} + +JSObject* +CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return dom::CanvasCaptureMediaStreamBinding::Wrap(aCx, this, aGivenProto); +} + +void +CanvasCaptureMediaStream::RequestFrame() +{ + if (mOutputStreamDriver) { + mOutputStreamDriver->RequestFrameCapture(); + } +} + +nsresult +CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS, + const TrackID& aTrackId, + nsIPrincipal* aPrincipal) +{ + PrincipalHandle principalHandle = MakePrincipalHandle(aPrincipal); + + if (!aFPS.WasPassed()) { + mOutputStreamDriver = + new AutoDriver(GetInputStream()->AsSourceStream(), aTrackId, principalHandle); + } 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(GetInputStream()->AsSourceStream(), fps, aTrackId, principalHandle); + } + return NS_OK; +} + +already_AddRefed<CanvasCaptureMediaStream> +CanvasCaptureMediaStream::CreateSourceStream(nsPIDOMWindowInner* aWindow, + HTMLCanvasElement* aCanvas) +{ + RefPtr<CanvasCaptureMediaStream> stream = new CanvasCaptureMediaStream(aWindow, aCanvas); + MediaStreamGraph* graph = + MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER, + AudioChannel::Normal); + stream->InitSourceStream(graph); + return stream.forget(); +} + +FrameCaptureListener* +CanvasCaptureMediaStream::FrameCaptureListener() +{ + return mOutputStreamDriver; +} + +void +CanvasCaptureMediaStream::StopCapture() +{ + if (!mOutputStreamDriver) { + return; + } + + mOutputStreamDriver->Forget(); + mOutputStreamDriver = nullptr; +} + +} // namespace dom +} // namespace mozilla + |