Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "CanvasCaptureMediaStream.h"
7 :
8 : #include "DOMMediaStream.h"
9 : #include "ImageContainer.h"
10 : #include "MediaStreamGraph.h"
11 : #include "MediaStreamListener.h"
12 : #include "gfxPlatform.h"
13 : #include "mozilla/Atomics.h"
14 : #include "mozilla/dom/CanvasCaptureMediaStreamBinding.h"
15 : #include "mozilla/gfx/2D.h"
16 : #include "nsContentUtils.h"
17 :
18 : using namespace mozilla::layers;
19 : using namespace mozilla::gfx;
20 :
21 : namespace mozilla {
22 : namespace dom {
23 :
24 : class OutputStreamDriver::StreamListener : public MediaStreamListener
25 : {
26 : public:
27 0 : explicit StreamListener(OutputStreamDriver* aDriver,
28 : TrackID aTrackId,
29 : PrincipalHandle aPrincipalHandle,
30 : SourceMediaStream* aSourceStream)
31 0 : : mEnded(false)
32 : , mSourceStream(aSourceStream)
33 : , mTrackId(aTrackId)
34 : , mPrincipalHandle(aPrincipalHandle)
35 0 : , mMutex("CanvasCaptureMediaStream OutputStreamDriver::StreamListener")
36 : {
37 0 : MOZ_ASSERT(mSourceStream);
38 0 : }
39 :
40 0 : void EndStream() {
41 0 : mEnded = true;
42 0 : }
43 :
44 0 : void SetImage(const RefPtr<layers::Image>& aImage, const TimeStamp& aTime)
45 : {
46 0 : MutexAutoLock lock(mMutex);
47 0 : mImage = aImage;
48 0 : mImageTime = aTime;
49 0 : }
50 :
51 0 : void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) override
52 : {
53 : // Called on the MediaStreamGraph thread.
54 0 : MOZ_ASSERT(mSourceStream);
55 0 : StreamTime delta = aDesiredTime - mSourceStream->GetEndOfAppendedData(mTrackId);
56 0 : if (delta > 0) {
57 0 : MutexAutoLock lock(mMutex);
58 :
59 0 : RefPtr<Image> image = mImage;
60 0 : IntSize size = image ? image->GetSize() : IntSize(0, 0);
61 0 : VideoSegment segment;
62 0 : segment.AppendFrame(image.forget(), delta, size, mPrincipalHandle, false,
63 0 : mImageTime);
64 :
65 0 : mSourceStream->AppendToTrack(mTrackId, &segment);
66 : }
67 :
68 0 : if (mEnded) {
69 0 : mSourceStream->EndAllTrackAndFinish();
70 : }
71 0 : }
72 :
73 : protected:
74 0 : ~StreamListener() { }
75 :
76 : private:
77 : Atomic<bool> mEnded;
78 : const RefPtr<SourceMediaStream> mSourceStream;
79 : const TrackID mTrackId;
80 : const PrincipalHandle mPrincipalHandle;
81 :
82 : Mutex mMutex;
83 : // The below members are protected by mMutex.
84 : RefPtr<layers::Image> mImage;
85 : TimeStamp mImageTime;
86 : };
87 :
88 0 : OutputStreamDriver::OutputStreamDriver(SourceMediaStream* aSourceStream,
89 : const TrackID& aTrackId,
90 0 : const PrincipalHandle& aPrincipalHandle)
91 : : FrameCaptureListener()
92 : , mSourceStream(aSourceStream)
93 : , mStreamListener(new StreamListener(this, aTrackId, aPrincipalHandle,
94 0 : aSourceStream))
95 : {
96 0 : MOZ_ASSERT(NS_IsMainThread());
97 0 : MOZ_ASSERT(mSourceStream);
98 0 : mSourceStream->AddListener(mStreamListener);
99 0 : mSourceStream->AddTrack(aTrackId, 0, new VideoSegment());
100 0 : mSourceStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
101 0 : mSourceStream->SetPullEnabled(true);
102 :
103 : // All CanvasCaptureMediaStreams shall at least get one frame.
104 0 : mFrameCaptureRequested = true;
105 0 : }
106 :
107 0 : OutputStreamDriver::~OutputStreamDriver()
108 : {
109 0 : MOZ_ASSERT(NS_IsMainThread());
110 0 : if (mStreamListener) {
111 : // MediaStreamGraph will keep the listener alive until it can finish the
112 : // stream on the next NotifyPull().
113 0 : mStreamListener->EndStream();
114 : }
115 0 : }
116 :
117 : void
118 0 : OutputStreamDriver::SetImage(const RefPtr<layers::Image>& aImage,
119 : const TimeStamp& aTime)
120 : {
121 0 : if (mStreamListener) {
122 0 : mStreamListener->SetImage(aImage, aTime);
123 : }
124 0 : }
125 :
126 : // ----------------------------------------------------------------------
127 :
128 : class TimerDriver : public OutputStreamDriver
129 : {
130 : public:
131 0 : explicit TimerDriver(SourceMediaStream* aSourceStream,
132 : const double& aFPS,
133 : const TrackID& aTrackId,
134 : const PrincipalHandle& aPrincipalHandle)
135 0 : : OutputStreamDriver(aSourceStream, aTrackId, aPrincipalHandle)
136 0 : , mFPS(aFPS)
137 0 : , mTimer(nullptr)
138 : {
139 0 : if (mFPS == 0.0) {
140 0 : return;
141 : }
142 :
143 0 : mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
144 0 : if (!mTimer) {
145 0 : return;
146 : }
147 0 : mTimer->InitWithNamedFuncCallback(&TimerTick,
148 : this,
149 0 : int(1000 / mFPS),
150 : nsITimer::TYPE_REPEATING_SLACK,
151 0 : "dom::TimerDriver::TimerDriver");
152 : }
153 :
154 0 : static void TimerTick(nsITimer* aTimer, void* aClosure)
155 : {
156 0 : MOZ_ASSERT(aClosure);
157 0 : TimerDriver* driver = static_cast<TimerDriver*>(aClosure);
158 :
159 0 : driver->RequestFrameCapture();
160 0 : }
161 :
162 0 : void NewFrame(already_AddRefed<Image> aImage, const TimeStamp& aTime) override
163 : {
164 0 : RefPtr<Image> image = aImage;
165 :
166 0 : if (!mFrameCaptureRequested) {
167 0 : return;
168 : }
169 :
170 0 : mFrameCaptureRequested = false;
171 0 : SetImage(image.forget(), aTime);
172 : }
173 :
174 0 : void Forget() override
175 : {
176 0 : if (mTimer) {
177 0 : mTimer->Cancel();
178 0 : mTimer = nullptr;
179 : }
180 0 : }
181 :
182 : protected:
183 0 : virtual ~TimerDriver() {}
184 :
185 : private:
186 : const double mFPS;
187 : nsCOMPtr<nsITimer> mTimer;
188 : };
189 :
190 : // ----------------------------------------------------------------------
191 :
192 : class AutoDriver : public OutputStreamDriver
193 : {
194 : public:
195 0 : explicit AutoDriver(SourceMediaStream* aSourceStream,
196 : const TrackID& aTrackId,
197 : const PrincipalHandle& aPrincipalHandle)
198 0 : : OutputStreamDriver(aSourceStream, aTrackId, aPrincipalHandle) {}
199 :
200 0 : void NewFrame(already_AddRefed<Image> aImage, const TimeStamp& aTime) override
201 : {
202 : // Don't reset `mFrameCaptureRequested` since AutoDriver shall always have
203 : // `mFrameCaptureRequested` set to true.
204 : // This also means we should accept every frame as NewFrame is called only
205 : // after something changed.
206 :
207 0 : RefPtr<Image> image = aImage;
208 0 : SetImage(image.forget(), aTime);
209 0 : }
210 :
211 : protected:
212 0 : virtual ~AutoDriver() {}
213 : };
214 :
215 : // ----------------------------------------------------------------------
216 :
217 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream, DOMMediaStream,
218 : mCanvas)
219 :
220 0 : NS_IMPL_ADDREF_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
221 0 : NS_IMPL_RELEASE_INHERITED(CanvasCaptureMediaStream, DOMMediaStream)
222 :
223 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(CanvasCaptureMediaStream)
224 0 : NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
225 :
226 0 : CanvasCaptureMediaStream::CanvasCaptureMediaStream(nsPIDOMWindowInner* aWindow,
227 0 : HTMLCanvasElement* aCanvas)
228 : : DOMMediaStream(aWindow, nullptr)
229 : , mCanvas(aCanvas)
230 0 : , mOutputStreamDriver(nullptr)
231 : {
232 0 : }
233 :
234 0 : CanvasCaptureMediaStream::~CanvasCaptureMediaStream()
235 : {
236 0 : if (mOutputStreamDriver) {
237 0 : mOutputStreamDriver->Forget();
238 : }
239 0 : }
240 :
241 : JSObject*
242 0 : CanvasCaptureMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
243 : {
244 0 : return dom::CanvasCaptureMediaStreamBinding::Wrap(aCx, this, aGivenProto);
245 : }
246 :
247 : void
248 0 : CanvasCaptureMediaStream::RequestFrame()
249 : {
250 0 : if (mOutputStreamDriver) {
251 0 : mOutputStreamDriver->RequestFrameCapture();
252 : }
253 0 : }
254 :
255 : nsresult
256 0 : CanvasCaptureMediaStream::Init(const dom::Optional<double>& aFPS,
257 : const TrackID& aTrackId,
258 : nsIPrincipal* aPrincipal)
259 : {
260 0 : PrincipalHandle principalHandle = MakePrincipalHandle(aPrincipal);
261 :
262 0 : if (!aFPS.WasPassed()) {
263 : mOutputStreamDriver =
264 0 : new AutoDriver(GetInputStream()->AsSourceStream(), aTrackId, principalHandle);
265 0 : } else if (aFPS.Value() < 0) {
266 0 : return NS_ERROR_ILLEGAL_VALUE;
267 : } else {
268 : // Cap frame rate to 60 FPS for sanity
269 0 : double fps = std::min(60.0, aFPS.Value());
270 : mOutputStreamDriver =
271 0 : new TimerDriver(GetInputStream()->AsSourceStream(), fps, aTrackId, principalHandle);
272 : }
273 0 : return NS_OK;
274 : }
275 :
276 : already_AddRefed<CanvasCaptureMediaStream>
277 0 : CanvasCaptureMediaStream::CreateSourceStream(nsPIDOMWindowInner* aWindow,
278 : HTMLCanvasElement* aCanvas)
279 : {
280 0 : RefPtr<CanvasCaptureMediaStream> stream = new CanvasCaptureMediaStream(aWindow, aCanvas);
281 : MediaStreamGraph* graph =
282 : MediaStreamGraph::GetInstance(MediaStreamGraph::SYSTEM_THREAD_DRIVER,
283 : AudioChannel::Normal,
284 0 : aWindow);
285 0 : stream->InitSourceStream(graph);
286 0 : return stream.forget();
287 : }
288 :
289 : FrameCaptureListener*
290 0 : CanvasCaptureMediaStream::FrameCaptureListener()
291 : {
292 0 : return mOutputStreamDriver;
293 : }
294 :
295 : void
296 0 : CanvasCaptureMediaStream::StopCapture()
297 : {
298 0 : if (!mOutputStreamDriver) {
299 0 : return;
300 : }
301 :
302 0 : mOutputStreamDriver->Forget();
303 0 : mOutputStreamDriver = nullptr;
304 : }
305 :
306 : } // namespace dom
307 : } // namespace mozilla
308 :
|