OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/browser/renderer_host/media/media_stream_ui_controller.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/callback.h" | |
11 #include "base/logging.h" | |
12 #include "base/message_loop_proxy.h" | |
13 #include "base/stl_util.h" | |
14 #include "content/browser/renderer_host/media/media_stream_settings_requester.h" | |
15 #include "content/browser/renderer_host/render_view_host_delegate.h" | |
16 #include "content/browser/renderer_host/render_view_host_impl.h" | |
17 #include "content/common/media/media_stream_options.h" | |
18 #include "content/public/browser/browser_thread.h" | |
19 #include "content/public/browser/content_browser_client.h" | |
20 #include "content/public/browser/media_observer.h" | |
21 #include "content/public/common/media_stream_request.h" | |
22 #include "googleurl/src/gurl.h" | |
23 #include "media/base/bind_to_loop.h" | |
24 | |
25 namespace content { | |
26 | |
27 // UI request contains all data needed to keep track of requests between the | |
28 // different calls. | |
29 class MediaStreamRequestForUI : public MediaStreamRequest { | |
30 public: | |
31 MediaStreamRequestForUI(int render_pid, | |
32 int render_vid, | |
33 const GURL& origin, | |
34 const StreamOptions& options, | |
35 MediaStreamRequestType request_type, | |
36 const std::string& requested_device_id) | |
37 : MediaStreamRequest(render_pid, render_vid, origin, | |
38 request_type, requested_device_id, | |
39 options.audio_type, options.video_type), | |
40 posted_task(false) { | |
41 DCHECK(IsAudioMediaType(options.audio_type) || | |
42 IsVideoMediaType(options.video_type)); | |
43 } | |
44 | |
45 ~MediaStreamRequestForUI() {} | |
46 | |
47 // Whether or not a task was posted to make the call to | |
48 // RequestMediaAccessPermission, to make sure that we never post twice to it. | |
49 bool posted_task; | |
50 }; | |
51 | |
52 namespace { | |
53 | |
54 // Sends the request to the appropriate WebContents. | |
55 void ProceedMediaAccessPermission(const MediaStreamRequestForUI& request, | |
56 const MediaResponseCallback& callback) { | |
57 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
58 | |
59 // Send the permission request to the web contents. | |
60 RenderViewHostImpl* host = RenderViewHostImpl::FromID( | |
61 request.render_process_id, request.render_view_id); | |
62 | |
63 // Tab may have gone away. | |
64 if (!host || !host->GetDelegate()) { | |
65 callback.Run(MediaStreamDevices(), scoped_ptr<MediaStreamUI>()); | |
66 return; | |
67 } | |
68 | |
69 host->GetDelegate()->RequestMediaAccessPermission(request, callback); | |
70 } | |
71 | |
72 } // namespace | |
73 | |
74 MediaStreamUIController::MediaStreamUIController(SettingsRequester* requester) | |
75 : requester_(requester), | |
76 use_fake_ui_(false) { | |
77 DCHECK(requester_); | |
78 } | |
79 | |
80 MediaStreamUIController::~MediaStreamUIController() { | |
81 DCHECK(requests_.empty()); | |
82 DCHECK(stream_indicators_.empty()); | |
83 } | |
84 | |
85 void MediaStreamUIController::MakeUIRequest( | |
86 const std::string& label, | |
87 int render_process_id, | |
88 int render_view_id, | |
89 const StreamOptions& request_options, | |
90 const GURL& security_origin, MediaStreamRequestType request_type, | |
91 const std::string& requested_device_id) { | |
92 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
93 | |
94 // Create a new request. | |
95 if (!requests_.insert( | |
96 std::make_pair(label, new MediaStreamRequestForUI( | |
97 render_process_id, render_view_id, security_origin, | |
98 request_options, request_type, requested_device_id))).second) { | |
99 NOTREACHED(); | |
100 } | |
101 | |
102 if (use_fake_ui_) { | |
103 PostRequestToFakeUI(label); | |
104 return; | |
105 } | |
106 | |
107 // The UI can handle only one request at the time, do not post the | |
108 // request to the view if the UI is handling any other request. | |
109 if (IsUIBusy(render_process_id, render_view_id)) | |
110 return; | |
111 | |
112 PostRequestToUI(label); | |
113 } | |
114 | |
115 void MediaStreamUIController::CancelUIRequest(const std::string& label) { | |
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
117 UIRequests::iterator request_iter = requests_.find(label); | |
118 if (request_iter != requests_.end()) { | |
119 // Proceed the next pending request for the same page. | |
120 scoped_ptr<MediaStreamRequestForUI> request(request_iter->second); | |
121 int render_view_id = request->render_view_id; | |
122 int render_process_id = request->render_process_id; | |
123 bool was_posted = request->posted_task; | |
124 | |
125 // TODO(xians): Post a cancel request on UI thread to dismiss the infobar | |
126 // if request has been sent to the UI. | |
127 // Remove the request from the queue. | |
128 requests_.erase(request_iter); | |
129 | |
130 // Simply return if the canceled request has not been brought to UI. | |
131 if (!was_posted) | |
132 return; | |
133 | |
134 // Process the next pending request to replace the old infobar on the same | |
135 // page. | |
136 ProcessNextRequestForView(render_process_id, render_view_id); | |
137 } | |
138 | |
139 NotifyUIIndicatorDevicesClosed(label); | |
140 } | |
141 | |
142 void MediaStreamUIController::ProcessAccessRequestResponse( | |
143 const std::string& label, | |
144 const MediaStreamDevices& devices, | |
145 scoped_ptr<MediaStreamUI> stream_ui) { | |
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
147 | |
148 UIRequests::iterator request_iter = requests_.find(label); | |
149 // Return if the request has been removed. | |
150 if (request_iter == requests_.end()) { | |
151 if (stream_ui) { | |
152 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, | |
153 stream_ui.release()); | |
154 } | |
155 return; | |
156 } | |
157 | |
158 DCHECK(requester_); | |
159 scoped_ptr<MediaStreamRequestForUI> request(request_iter->second); | |
160 requests_.erase(request_iter); | |
161 | |
162 // Look for queued requests for the same view. If there is a pending request, | |
163 // post it for user approval. | |
164 ProcessNextRequestForView(request->render_process_id, | |
165 request->render_view_id); | |
166 | |
167 if (!devices.empty()) { | |
168 if (stream_ui) { | |
169 DCHECK(stream_indicators_.find(label) == stream_indicators_.end()); | |
170 stream_indicators_[label] = stream_ui.release(); | |
171 } | |
172 | |
173 // Build a list of "full" device objects for the accepted devices. | |
174 StreamDeviceInfoArray device_list; | |
175 // TODO(xians): figure out if it is all right to hard code in_use to false, | |
176 // though DevicesAccepted seems to do so. | |
177 for (MediaStreamDevices::const_iterator dev = devices.begin(); | |
178 dev != devices.end(); ++dev) { | |
179 device_list.push_back(StreamDeviceInfo( | |
180 dev->type, dev->name, dev->id, | |
181 dev->sample_rate, dev->channel_layout, false)); | |
182 } | |
183 | |
184 requester_->DevicesAccepted(label, device_list); | |
185 } else { | |
186 DCHECK(!stream_ui); | |
187 requester_->SettingsError(label); | |
188 } | |
189 } | |
190 | |
191 void MediaStreamUIController::NotifyUIIndicatorDevicesOpened( | |
192 const std::string& label) { | |
193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
194 | |
195 IndicatorsMap::iterator it = stream_indicators_.find(label); | |
196 if (it != stream_indicators_.end()) { | |
197 base::Closure stop_callback = media::BindToLoop( | |
198 base::MessageLoopProxy::current(), | |
199 base::Bind(&MediaStreamUIController::OnStopStreamFromUI, | |
200 base::Unretained(this), label)); | |
201 | |
202 // base::Unretained is safe here because the target can be deleted only on | |
203 // UI thread when posted from IO thread (see | |
204 // NotifyUIIndicatorDevicesClosed()). | |
205 BrowserThread::PostTask( | |
206 BrowserThread::UI, FROM_HERE, | |
207 base::Bind(&MediaStreamUI::OnStarted, | |
208 base::Unretained(it->second), stop_callback)); | |
209 } | |
210 } | |
211 | |
212 void MediaStreamUIController::NotifyUIIndicatorDevicesClosed( | |
213 const std::string& label) { | |
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
215 | |
216 IndicatorsMap::iterator indicator = stream_indicators_.find(label); | |
217 if (indicator != stream_indicators_.end()) { | |
218 BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, indicator->second); | |
219 stream_indicators_.erase(indicator); | |
220 } | |
221 } | |
222 | |
223 void MediaStreamUIController::UseFakeUI(scoped_ptr<MediaStreamUI> fake_ui) { | |
224 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
225 use_fake_ui_ = true; | |
226 fake_ui_ = fake_ui.Pass(); | |
227 } | |
228 | |
229 bool MediaStreamUIController::IsUIBusy(int render_process_id, | |
230 int render_view_id) { | |
231 for (UIRequests::iterator it = requests_.begin(); | |
232 it != requests_.end(); ++it) { | |
233 if (it->second->render_process_id == render_process_id && | |
234 it->second->render_view_id == render_view_id && | |
235 it->second->posted_task) { | |
236 return true; | |
237 } | |
238 } | |
239 return false; | |
240 } | |
241 | |
242 void MediaStreamUIController::ProcessNextRequestForView( | |
243 int render_process_id, | |
244 int render_view_id) { | |
245 std::string next_request_label; | |
246 for (UIRequests::iterator it = requests_.begin(); it != requests_.end(); | |
247 ++it) { | |
248 if (it->second->render_process_id == render_process_id && | |
249 it->second->render_view_id == render_view_id) { | |
250 // This request belongs to the given render view. | |
251 if (!it->second->posted_task) { | |
252 next_request_label = it->first; | |
253 break; | |
254 } | |
255 } | |
256 } | |
257 | |
258 if (next_request_label.empty()) | |
259 return; | |
260 | |
261 if (fake_ui_) { | |
262 PostRequestToFakeUI(next_request_label); | |
263 } else { | |
264 PostRequestToUI(next_request_label); | |
265 } | |
266 } | |
267 | |
268 void MediaStreamUIController::PostRequestToUI(const std::string& label) { | |
269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
270 UIRequests::iterator request_iter = requests_.find(label); | |
271 | |
272 if (request_iter == requests_.end()) { | |
273 NOTREACHED(); | |
274 return; | |
275 } | |
276 MediaStreamRequestForUI* request = request_iter->second; | |
277 DCHECK(request != NULL); | |
278 | |
279 request->posted_task = true; | |
280 | |
281 BrowserThread::PostTask( | |
282 BrowserThread::UI, FROM_HERE, base::Bind( | |
283 &ProceedMediaAccessPermission, *request, media::BindToLoop( | |
284 base::MessageLoopProxy::current(), base::Bind( | |
285 &MediaStreamUIController::ProcessAccessRequestResponse, | |
286 base::Unretained(this), label)))); | |
287 } | |
288 | |
289 void MediaStreamUIController::PostRequestToFakeUI(const std::string& label) { | |
290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
291 DCHECK(requester_); | |
292 UIRequests::iterator request_iter = requests_.find(label); | |
293 DCHECK(request_iter != requests_.end()); | |
294 MediaStreamRequestForUI* request = request_iter->second; | |
295 | |
296 MediaStreamDevices devices; | |
297 requester_->GetAvailableDevices(&devices); | |
298 MediaStreamDevices devices_to_use; | |
299 bool accepted_audio = false; | |
300 bool accepted_video = false; | |
301 // Use the first capture device of the same media type in the list for the | |
302 // fake UI. | |
303 for (MediaStreamDevices::const_iterator it = devices.begin(); | |
304 it != devices.end(); ++it) { | |
305 if (!accepted_audio && | |
306 IsAudioMediaType(request->audio_type) && | |
307 IsAudioMediaType(it->type)) { | |
308 devices_to_use.push_back(*it); | |
309 accepted_audio = true; | |
310 } else if (!accepted_video && | |
311 IsVideoMediaType(request->video_type) && | |
312 IsVideoMediaType(it->type)) { | |
313 devices_to_use.push_back(*it); | |
314 accepted_video = true; | |
315 } | |
316 } | |
317 | |
318 BrowserThread::PostTask( | |
319 BrowserThread::IO, FROM_HERE, | |
320 base::Bind(&MediaStreamUIController::ProcessAccessRequestResponse, | |
321 base::Unretained(this), label, devices_to_use, | |
322 base::Passed(&fake_ui_))); | |
323 } | |
324 | |
325 void MediaStreamUIController::OnStopStreamFromUI(const std::string& label) { | |
326 // It's safe to base::Unretained() here because |requester_| references | |
327 // MediaStreamManager which always outlives IO thread. | |
328 // | |
329 // TODO(sergeyu): Refactor this code to not rely on what |requester_| is. | |
330 BrowserThread::PostTask( | |
331 BrowserThread::IO, FROM_HERE, | |
332 base::Bind(&SettingsRequester::StopStreamFromUI, | |
333 base::Unretained(requester_), label)); | |
334 } | |
335 | |
336 } // namespace content | |
OLD | NEW |