/src/mozilla-central/dom/media/webrtc/MediaEngineTabVideoSource.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 "MediaEngineTabVideoSource.h" |
7 | | |
8 | | #include "mozilla/gfx/2D.h" |
9 | | #include "mozilla/gfx/DataSurfaceHelpers.h" |
10 | | #include "mozilla/RefPtr.h" |
11 | | #include "mozilla/UniquePtrExtensions.h" |
12 | | #include "mozilla/dom/BindingDeclarations.h" |
13 | | #include "nsGlobalWindow.h" |
14 | | #include "nsIDocShell.h" |
15 | | #include "nsIPresShell.h" |
16 | | #include "nsPresContext.h" |
17 | | #include "gfxContext.h" |
18 | | #include "gfx2DGlue.h" |
19 | | #include "AllocationHandle.h" |
20 | | #include "ImageContainer.h" |
21 | | #include "Layers.h" |
22 | | #include "nsIInterfaceRequestorUtils.h" |
23 | | #include "nsITabSource.h" |
24 | | #include "VideoUtils.h" |
25 | | #include "nsServiceManagerUtils.h" |
26 | | #include "nsIPrefService.h" |
27 | | #include "MediaTrackConstraints.h" |
28 | | #include "Tracing.h" |
29 | | |
30 | | namespace mozilla { |
31 | | |
32 | | using namespace mozilla::gfx; |
33 | | |
34 | | MediaEngineTabVideoSource::MediaEngineTabVideoSource() |
35 | 0 | : mMutex("MediaEngineTabVideoSource::mMutex") {} |
36 | | |
37 | | nsresult |
38 | | MediaEngineTabVideoSource::StartRunnable::Run() |
39 | 0 | { |
40 | 0 | mVideoSource->Draw(); |
41 | 0 | mVideoSource->mTimer->InitWithNamedFuncCallback( |
42 | 0 | [](nsITimer* aTimer, void* aClosure) mutable { |
43 | 0 | auto source = static_cast<MediaEngineTabVideoSource*>(aClosure); |
44 | 0 | source->Draw(); |
45 | 0 | }, |
46 | 0 | mVideoSource, |
47 | 0 | mVideoSource->mTimePerFrame, |
48 | 0 | nsITimer::TYPE_REPEATING_SLACK, |
49 | 0 | "MediaEngineTabVideoSource DrawTimer"); |
50 | 0 | if (mVideoSource->mTabSource) { |
51 | 0 | mVideoSource->mTabSource->NotifyStreamStart(mVideoSource->mWindow); |
52 | 0 | } |
53 | 0 | return NS_OK; |
54 | 0 | } |
55 | | |
56 | | nsresult |
57 | | MediaEngineTabVideoSource::StopRunnable::Run() |
58 | 0 | { |
59 | 0 | if (mVideoSource->mTimer) { |
60 | 0 | mVideoSource->mTimer->Cancel(); |
61 | 0 | mVideoSource->mTimer = nullptr; |
62 | 0 | } |
63 | 0 | if (mVideoSource->mTabSource) { |
64 | 0 | mVideoSource->mTabSource->NotifyStreamStop(mVideoSource->mWindow); |
65 | 0 | } |
66 | 0 | return NS_OK; |
67 | 0 | } |
68 | | |
69 | | nsresult |
70 | | MediaEngineTabVideoSource::InitRunnable::Run() |
71 | 0 | { |
72 | 0 | if (mVideoSource->mWindowId != -1) { |
73 | 0 | nsGlobalWindowOuter* globalWindow = |
74 | 0 | nsGlobalWindowOuter::GetOuterWindowWithId(mVideoSource->mWindowId); |
75 | 0 | if (!globalWindow) { |
76 | 0 | // We can't access the window, just send a blacked out screen. |
77 | 0 | mVideoSource->mWindow = nullptr; |
78 | 0 | mVideoSource->mBlackedoutWindow = true; |
79 | 0 | } else { |
80 | 0 | nsCOMPtr<nsPIDOMWindowOuter> window = globalWindow->AsOuter(); |
81 | 0 | if (window) { |
82 | 0 | mVideoSource->mWindow = window; |
83 | 0 | mVideoSource->mBlackedoutWindow = false; |
84 | 0 | } |
85 | 0 | } |
86 | 0 | } |
87 | 0 | if (!mVideoSource->mWindow && !mVideoSource->mBlackedoutWindow) { |
88 | 0 | nsresult rv; |
89 | 0 | mVideoSource->mTabSource = do_GetService(NS_TABSOURCESERVICE_CONTRACTID, &rv); |
90 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
91 | 0 |
|
92 | 0 | nsCOMPtr<mozIDOMWindowProxy> win; |
93 | 0 | rv = mVideoSource->mTabSource->GetTabToStream(getter_AddRefs(win)); |
94 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
95 | 0 | if (!win) |
96 | 0 | return NS_OK; |
97 | 0 | |
98 | 0 | mVideoSource->mWindow = nsPIDOMWindowOuter::From(win); |
99 | 0 | MOZ_ASSERT(mVideoSource->mWindow); |
100 | 0 | } |
101 | 0 | mVideoSource->mTimer = NS_NewTimer(); |
102 | 0 | nsCOMPtr<nsIRunnable> start(new StartRunnable(mVideoSource)); |
103 | 0 | start->Run(); |
104 | 0 | return NS_OK; |
105 | 0 | } |
106 | | |
107 | | nsresult |
108 | | MediaEngineTabVideoSource::DestroyRunnable::Run() |
109 | 0 | { |
110 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
111 | 0 |
|
112 | 0 | mVideoSource->mWindow = nullptr; |
113 | 0 | mVideoSource->mTabSource = nullptr; |
114 | 0 |
|
115 | 0 | return NS_OK; |
116 | 0 | } |
117 | | |
118 | | nsString |
119 | | MediaEngineTabVideoSource::GetName() const |
120 | 0 | { |
121 | 0 | return NS_LITERAL_STRING(u"&getUserMedia.videoSource.tabShare;"); |
122 | 0 | } |
123 | | |
124 | | nsCString |
125 | | MediaEngineTabVideoSource::GetUUID() const |
126 | 0 | { |
127 | 0 | return NS_LITERAL_CSTRING("tab"); |
128 | 0 | } |
129 | | |
130 | 0 | #define DEFAULT_TABSHARE_VIDEO_MAX_WIDTH 4096 |
131 | 0 | #define DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT 4096 |
132 | 0 | #define DEFAULT_TABSHARE_VIDEO_FRAMERATE 30 |
133 | | |
134 | | nsresult |
135 | | MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints, |
136 | | const MediaEnginePrefs& aPrefs, |
137 | | const nsString& aDeviceId, |
138 | | const mozilla::ipc::PrincipalInfo& aPrincipalInfo, |
139 | | AllocationHandle** aOutHandle, |
140 | | const char** aOutBadConstraint) |
141 | 0 | { |
142 | 0 | AssertIsOnOwningThread(); |
143 | 0 |
|
144 | 0 | // windowId is not a proper constraint, so just read it. |
145 | 0 | // It has no well-defined behavior in advanced, so ignore it there. |
146 | 0 |
|
147 | 0 | mWindowId = aConstraints.mBrowserWindow.WasPassed() ? |
148 | 0 | aConstraints.mBrowserWindow.Value() : -1; |
149 | 0 | *aOutHandle = nullptr; |
150 | 0 |
|
151 | 0 | { |
152 | 0 | MutexAutoLock lock(mMutex); |
153 | 0 | mState = kAllocated; |
154 | 0 | } |
155 | 0 |
|
156 | 0 | return Reconfigure(nullptr, aConstraints, aPrefs, aDeviceId, aOutBadConstraint); |
157 | 0 | } |
158 | | |
159 | | nsresult |
160 | | MediaEngineTabVideoSource::Reconfigure(const RefPtr<AllocationHandle>& aHandle, |
161 | | const dom::MediaTrackConstraints& aConstraints, |
162 | | const mozilla::MediaEnginePrefs& aPrefs, |
163 | | const nsString& aDeviceId, |
164 | | const char** aOutBadConstraint) |
165 | 0 | { |
166 | 0 | AssertIsOnOwningThread(); |
167 | 0 | MOZ_ASSERT(!aHandle); |
168 | 0 | MOZ_ASSERT(mState != kReleased); |
169 | 0 |
|
170 | 0 | // scrollWithPage is not proper a constraint, so just read it. |
171 | 0 | // It has no well-defined behavior in advanced, so ignore it there. |
172 | 0 |
|
173 | 0 | mScrollWithPage = aConstraints.mScrollWithPage.WasPassed() ? |
174 | 0 | aConstraints.mScrollWithPage.Value() : false; |
175 | 0 |
|
176 | 0 | FlattenedConstraints c(aConstraints); |
177 | 0 |
|
178 | 0 | mBufWidthMax = c.mWidth.Get(DEFAULT_TABSHARE_VIDEO_MAX_WIDTH); |
179 | 0 | mBufHeightMax = c.mHeight.Get(DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT); |
180 | 0 | double frameRate = c.mFrameRate.Get(DEFAULT_TABSHARE_VIDEO_FRAMERATE); |
181 | 0 | mTimePerFrame = std::max(10, int(1000.0 / (frameRate > 0? frameRate : 1))); |
182 | 0 |
|
183 | 0 | if (!mScrollWithPage) { |
184 | 0 | mViewportOffsetX = c.mViewportOffsetX.Get(0); |
185 | 0 | mViewportOffsetY = c.mViewportOffsetY.Get(0); |
186 | 0 | mViewportWidth = c.mViewportWidth.Get(INT32_MAX); |
187 | 0 | mViewportHeight = c.mViewportHeight.Get(INT32_MAX); |
188 | 0 | } |
189 | 0 | return NS_OK; |
190 | 0 | } |
191 | | |
192 | | nsresult |
193 | | MediaEngineTabVideoSource::Deallocate(const RefPtr<const AllocationHandle>& aHandle) |
194 | 0 | { |
195 | 0 | AssertIsOnOwningThread(); |
196 | 0 | MOZ_ASSERT(!aHandle); |
197 | 0 | MOZ_ASSERT(mState == kAllocated || mState == kStopped); |
198 | 0 |
|
199 | 0 | if (mStream && IsTrackIDExplicit(mTrackID)) { |
200 | 0 | mStream->EndTrack(mTrackID); |
201 | 0 | } |
202 | 0 |
|
203 | 0 | NS_DispatchToMainThread(do_AddRef(new DestroyRunnable(this))); |
204 | 0 |
|
205 | 0 | { |
206 | 0 | MutexAutoLock lock(mMutex); |
207 | 0 | mState = kReleased; |
208 | 0 | } |
209 | 0 |
|
210 | 0 | return NS_OK; |
211 | 0 | } |
212 | | |
213 | | nsresult |
214 | | MediaEngineTabVideoSource::SetTrack(const RefPtr<const AllocationHandle>& aHandle, |
215 | | const RefPtr<SourceMediaStream>& aStream, |
216 | | TrackID aTrackID, |
217 | | const mozilla::PrincipalHandle& aPrincipal) |
218 | 0 | { |
219 | 0 | AssertIsOnOwningThread(); |
220 | 0 | MOZ_ASSERT(mState == kAllocated); |
221 | 0 |
|
222 | 0 | MOZ_ASSERT(!mStream); |
223 | 0 | MOZ_ASSERT(mTrackID == TRACK_NONE); |
224 | 0 | MOZ_ASSERT(aStream); |
225 | 0 | MOZ_ASSERT(IsTrackIDExplicit(aTrackID)); |
226 | 0 | mStream = aStream; |
227 | 0 | mTrackID = aTrackID; |
228 | 0 | mStream->AddTrack(mTrackID, 0, new VideoSegment()); |
229 | 0 | return NS_OK; |
230 | 0 | } |
231 | | |
232 | | nsresult |
233 | | MediaEngineTabVideoSource::Start(const RefPtr<const AllocationHandle>& aHandle) |
234 | 0 | { |
235 | 0 | AssertIsOnOwningThread(); |
236 | 0 | MOZ_ASSERT(mState == kAllocated); |
237 | 0 |
|
238 | 0 | nsCOMPtr<nsIRunnable> runnable; |
239 | 0 | if (!mWindow) { |
240 | 0 | runnable = new InitRunnable(this); |
241 | 0 | } else { |
242 | 0 | runnable = new StartRunnable(this); |
243 | 0 | } |
244 | 0 | NS_DispatchToMainThread(runnable); |
245 | 0 |
|
246 | 0 | { |
247 | 0 | MutexAutoLock lock(mMutex); |
248 | 0 | mState = kStarted; |
249 | 0 | } |
250 | 0 |
|
251 | 0 | return NS_OK; |
252 | 0 | } |
253 | | |
254 | | void |
255 | | MediaEngineTabVideoSource::Pull(const RefPtr<const AllocationHandle>& aHandle, |
256 | | const RefPtr<SourceMediaStream>& aStream, |
257 | | TrackID aTrackID, |
258 | | StreamTime aDesiredTime, |
259 | | const PrincipalHandle& aPrincipalHandle) |
260 | 0 | { |
261 | 0 | TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p track %i", |
262 | 0 | aStream.get(), aTrackID); |
263 | 0 | VideoSegment segment; |
264 | 0 | RefPtr<layers::Image> image; |
265 | 0 | gfx::IntSize imageSize; |
266 | 0 |
|
267 | 0 | { |
268 | 0 | MutexAutoLock lock(mMutex); |
269 | 0 | if (mState == kReleased) { |
270 | 0 | // We end the track before setting the state to released. |
271 | 0 | return; |
272 | 0 | } |
273 | 0 | if (mState == kStarted) { |
274 | 0 | image = mImage; |
275 | 0 | imageSize = mImageSize; |
276 | 0 | } |
277 | 0 | } |
278 | 0 |
|
279 | 0 | StreamTime delta = aDesiredTime - aStream->GetEndOfAppendedData(aTrackID); |
280 | 0 | if (delta <= 0) { |
281 | 0 | return; |
282 | 0 | } |
283 | 0 | |
284 | 0 | // nullptr images are allowed |
285 | 0 | segment.AppendFrame(image.forget(), delta, imageSize, aPrincipalHandle); |
286 | 0 | // This can fail if either a) we haven't added the track yet, or b) |
287 | 0 | // we've removed or ended the track. |
288 | 0 | aStream->AppendToTrack(aTrackID, &(segment)); |
289 | 0 | } |
290 | | |
291 | | void |
292 | 0 | MediaEngineTabVideoSource::Draw() { |
293 | 0 | if (!mWindow && !mBlackedoutWindow) { |
294 | 0 | return; |
295 | 0 | } |
296 | 0 | |
297 | 0 | if (mWindow) { |
298 | 0 | if (mScrollWithPage || mViewportWidth == INT32_MAX) { |
299 | 0 | mWindow->GetInnerWidth(&mViewportWidth); |
300 | 0 | } |
301 | 0 | if (mScrollWithPage || mViewportHeight == INT32_MAX) { |
302 | 0 | mWindow->GetInnerHeight(&mViewportHeight); |
303 | 0 | } |
304 | 0 | if (!mViewportWidth || !mViewportHeight) { |
305 | 0 | return; |
306 | 0 | } |
307 | 0 | } else { |
308 | 0 | mViewportWidth = 640; |
309 | 0 | mViewportHeight = 480; |
310 | 0 | } |
311 | 0 |
|
312 | 0 | IntSize size; |
313 | 0 | { |
314 | 0 | float pixelRatio; |
315 | 0 | if (mWindow) { |
316 | 0 | pixelRatio = mWindow->GetDevicePixelRatio(dom::CallerType::System); |
317 | 0 | } else { |
318 | 0 | pixelRatio = 1.0f; |
319 | 0 | } |
320 | 0 | const int32_t deviceWidth = (int32_t)(pixelRatio * mViewportWidth); |
321 | 0 | const int32_t deviceHeight = (int32_t)(pixelRatio * mViewportHeight); |
322 | 0 |
|
323 | 0 | if ((deviceWidth <= mBufWidthMax) && (deviceHeight <= mBufHeightMax)) { |
324 | 0 | size = IntSize(deviceWidth, deviceHeight); |
325 | 0 | } else { |
326 | 0 | const float scaleWidth = (float)mBufWidthMax / (float)deviceWidth; |
327 | 0 | const float scaleHeight = (float)mBufHeightMax / (float)deviceHeight; |
328 | 0 | const float scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight; |
329 | 0 |
|
330 | 0 | size = IntSize((int)(scale * deviceWidth), (int)(scale * deviceHeight)); |
331 | 0 | } |
332 | 0 | } |
333 | 0 |
|
334 | 0 | uint32_t stride = StrideForFormatAndWidth(SurfaceFormat::X8R8G8B8_UINT32, |
335 | 0 | size.width); |
336 | 0 |
|
337 | 0 | if (mDataSize < static_cast<size_t>(stride * size.height)) { |
338 | 0 | mDataSize = stride * size.height; |
339 | 0 | mData = MakeUniqueFallible<unsigned char[]>(mDataSize); |
340 | 0 | } |
341 | 0 | if (!mData) { |
342 | 0 | return; |
343 | 0 | } |
344 | 0 | |
345 | 0 | nsCOMPtr<nsIPresShell> presShell; |
346 | 0 | if (mWindow) { |
347 | 0 | RefPtr<nsPresContext> presContext; |
348 | 0 | nsIDocShell* docshell = mWindow->GetDocShell(); |
349 | 0 | if (docshell) { |
350 | 0 | docshell->GetPresContext(getter_AddRefs(presContext)); |
351 | 0 | } |
352 | 0 | if (!presContext) { |
353 | 0 | return; |
354 | 0 | } |
355 | 0 | presShell = presContext->PresShell(); |
356 | 0 | } |
357 | 0 |
|
358 | 0 | RefPtr<layers::ImageContainer> container = |
359 | 0 | layers::LayerManager::CreateImageContainer(layers::ImageContainer::ASYNCHRONOUS); |
360 | 0 | RefPtr<DrawTarget> dt = |
361 | 0 | Factory::CreateDrawTargetForData(gfxPlatform::GetPlatform()->GetSoftwareBackend(), |
362 | 0 | mData.get(), |
363 | 0 | size, |
364 | 0 | stride, |
365 | 0 | SurfaceFormat::B8G8R8X8, |
366 | 0 | true); |
367 | 0 | if (!dt || !dt->IsValid()) { |
368 | 0 | return; |
369 | 0 | } |
370 | 0 | |
371 | 0 | if (mWindow) { |
372 | 0 | RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); |
373 | 0 | MOZ_ASSERT(context); // already checked the draw target above |
374 | 0 | context->SetMatrix(context->CurrentMatrix().PreScale((((float) size.width)/mViewportWidth), |
375 | 0 | (((float) size.height)/mViewportHeight))); |
376 | 0 |
|
377 | 0 | nscolor bgColor = NS_RGB(255, 255, 255); |
378 | 0 | uint32_t renderDocFlags = mScrollWithPage? 0 : |
379 | 0 | (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | |
380 | 0 | nsIPresShell::RENDER_DOCUMENT_RELATIVE); |
381 | 0 | nsRect r(nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetX), |
382 | 0 | nsPresContext::CSSPixelsToAppUnits((float)mViewportOffsetY), |
383 | 0 | nsPresContext::CSSPixelsToAppUnits((float)mViewportWidth), |
384 | 0 | nsPresContext::CSSPixelsToAppUnits((float)mViewportHeight)); |
385 | 0 | NS_ENSURE_SUCCESS_VOID(presShell->RenderDocument(r, renderDocFlags, bgColor, context)); |
386 | 0 | } else { |
387 | 0 | dt->ClearRect(Rect(0, 0, size.width, size.height)); |
388 | 0 | } |
389 | 0 |
|
390 | 0 | RefPtr<SourceSurface> surface = dt->Snapshot(); |
391 | 0 | if (!surface) { |
392 | 0 | return; |
393 | 0 | } |
394 | 0 | |
395 | 0 | RefPtr<layers::SourceSurfaceImage> image = new layers::SourceSurfaceImage(size, surface); |
396 | 0 |
|
397 | 0 | MutexAutoLock lock(mMutex); |
398 | 0 | mImage = image; |
399 | 0 | mImageSize = size; |
400 | 0 | } |
401 | | |
402 | | nsresult |
403 | | MediaEngineTabVideoSource::FocusOnSelectedSource(const RefPtr<const AllocationHandle>& aHandle) |
404 | 0 | { |
405 | 0 | return NS_ERROR_NOT_IMPLEMENTED; |
406 | 0 | } |
407 | | |
408 | | nsresult |
409 | | MediaEngineTabVideoSource::Stop(const RefPtr<const AllocationHandle>& aHandle) |
410 | 0 | { |
411 | 0 | AssertIsOnOwningThread(); |
412 | 0 |
|
413 | 0 | if (mState == kStopped || mState == kAllocated) { |
414 | 0 | return NS_OK; |
415 | 0 | } |
416 | 0 | |
417 | 0 | MOZ_ASSERT(mState == kStarted); |
418 | 0 |
|
419 | 0 | // If mBlackedoutWindow is true, we may be running |
420 | 0 | // despite mWindow == nullptr. |
421 | 0 | if (!mWindow && !mBlackedoutWindow) { |
422 | 0 | return NS_OK; |
423 | 0 | } |
424 | 0 | |
425 | 0 | NS_DispatchToMainThread(new StopRunnable(this)); |
426 | 0 |
|
427 | 0 | { |
428 | 0 | MutexAutoLock lock(mMutex); |
429 | 0 | mState = kStopped; |
430 | 0 | } |
431 | 0 | return NS_OK; |
432 | 0 | } |
433 | | |
434 | | } |