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