| 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 |