1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/media/media_devices_manager.h"
14 #include "base/command_line.h"
15 #include "base/containers/contains.h"
16 #include "base/containers/cxx20_erase.h"
17 #include "base/functional/bind.h"
18 #include "base/location.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/ranges/algorithm.h"
21 #include "base/sequence_checker.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/task/bind_post_task.h"
24 #include "base/task/sequenced_task_runner.h"
25 #include "base/task/single_thread_task_runner.h"
26 #include "base/threading/thread_checker.h"
27 #include "build/build_config.h"
28 #include "build/chromeos_buildflags.h"
29 #include "content/browser/media/media_devices_permission_checker.h"
30 #include "content/browser/renderer_host/media/media_stream_manager.h"
31 #include "content/browser/renderer_host/media/video_capture_manager.h"
32 #include "content/common/features.h"
33 #include "content/public/browser/audio_service.h"
34 #include "content/public/browser/browser_task_traits.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/common/content_features.h"
37 #include "media/audio/audio_device_description.h"
38 #include "media/audio/audio_system.h"
39 #include "media/base/media_switches.h"
40 #include "mojo/public/cpp/bindings/receiver.h"
41 #include "mojo/public/cpp/bindings/remote.h"
42 #include "services/audio/public/mojom/device_notifications.mojom.h"
43 #include "third_party/blink/public/common/mediastream/media_devices.h"
44 #include "third_party/blink/public/mojom/mediastream/media_devices.mojom.h"
47 #include "base/functional/callback_helpers.h"
48 #include "base/task/single_thread_task_runner.h"
49 #include "content/browser/browser_main_loop.h"
50 #include "media/device_monitors/device_monitor_mac.h"
53 #if BUILDFLAG(IS_TIZEN_TV)
54 #include "tizen_src/ewk/efl_integration/common/application_type.h"
60 using media::mojom::DeviceEnumerationResult;
62 // Resolutions used if the source doesn't support capability enumeration.
66 } const kFallbackVideoResolutions[] = {{1920, 1080}, {1280, 720}, {640, 480}};
68 // Frame rates for sources with no support for capability enumeration.
69 const uint16_t kFallbackVideoFrameRates[] = {30, 60};
71 void SendLogMessage(const std::string& message) {
72 MediaStreamManager::SendMessageToNativeLog("MDM::" + message);
75 const char* DeviceTypeToString(MediaDeviceType device_type) {
76 switch (device_type) {
77 case MediaDeviceType::kMediaAudioInput:
79 case MediaDeviceType::kMediaVideoInput:
81 case MediaDeviceType::kMediaAudioOuput:
82 return "AUDIO_OUTPUT";
89 std::string GetDevicesEnumeratedLogString(
90 MediaDeviceType device_type,
91 const blink::WebMediaDeviceInfoArray& device_infos) {
92 std::string str = base::StringPrintf("DevicesEnumerated({type=%s}, ",
93 DeviceTypeToString(device_type));
94 base::StringAppendF(&str, "{labels=[");
95 for (const auto& device_info : device_infos)
96 base::StringAppendF(&str, "%s, ", device_info.label.c_str());
98 str.erase(str.end() - 2, str.end());
104 blink::WebMediaDeviceInfoArray GetFakeAudioDevices(bool is_input) {
105 blink::WebMediaDeviceInfoArray result;
107 result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId,
108 "Fake Default Audio Input",
109 "fake_group_audio_input_default");
110 result.emplace_back("fake_audio_input_1", "Fake Audio Input 1",
111 "fake_group_audio_input_1");
112 result.emplace_back("fake_audio_input_2", "Fake Audio Input 2",
113 "fake_group_audio_input_2");
115 result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId,
116 "Fake Default Audio Output",
117 "fake_group_audio_output_default");
118 result.emplace_back("fake_audio_output_1", "Fake Audio Output 1",
119 "fake_group_audio_output_1");
120 result.emplace_back("fake_audio_output_2", "Fake Audio Output 2",
121 "fake_group_audio_output_2");
127 std::string VideoLabelWithoutModelID(const std::string& label) {
128 if (label.rfind(")") != label.size() - 1)
131 auto idx = label.rfind(" (");
132 if (idx == std::string::npos)
135 return label.substr(0, idx - 1);
138 bool LabelHasUSBModel(const std::string& label) {
139 return label.size() >= 11 && label[label.size() - 11] == '(' &&
140 label[label.size() - 6] == ':' && label[label.size() - 1] == ')';
143 std::string GetUSBModelFromLabel(const std::string& label) {
144 DCHECK(LabelHasUSBModel(label));
145 return label.substr(label.size() - 10, 9);
148 bool IsRealAudioDeviceID(const std::string& device_id) {
149 return !media::AudioDeviceDescription::IsDefaultDevice(device_id) &&
150 !media::AudioDeviceDescription::IsCommunicationsDevice(device_id);
153 static bool EqualDeviceAndGroupID(const blink::WebMediaDeviceInfo& lhs,
154 const blink::WebMediaDeviceInfo& rhs) {
155 return lhs == rhs && lhs.group_id == rhs.group_id;
158 void ReplaceInvalidFrameRatesWithFallback(media::VideoCaptureFormats* formats) {
159 for (auto& format : *formats) {
160 if (format.frame_rate <= 0)
161 format.frame_rate = kFallbackVideoFrameRates[0];
165 void BindDeviceNotifierFromUIThread(
166 mojo::PendingReceiver<audio::mojom::DeviceNotifier> receiver) {
167 DCHECK_CURRENTLY_ON(BrowserThread::UI);
168 GetAudioService().BindDeviceNotifier(std::move(receiver));
171 void ReportVideoEnumerationStart() {
172 base::UmaHistogramBoolean(
173 "Media.MediaDevicesManager.VideoDeviceEnumeration.Start", true);
176 void ReportVideoEnumerationResult(DeviceEnumerationResult result_code) {
177 base::UmaHistogramEnumeration(
178 "Media.MediaDevicesManager.VideoDeviceEnumeration.Result", result_code);
183 std::string GuessVideoGroupID(const blink::WebMediaDeviceInfoArray& audio_infos,
184 const blink::WebMediaDeviceInfo& video_info) {
185 const std::string video_label = VideoLabelWithoutModelID(video_info.label);
187 // If |video_label| is very small, do not guess in order to avoid false
189 if (video_label.size() <= 3)
190 return video_info.device_id;
192 base::RepeatingCallback<bool(const blink::WebMediaDeviceInfo&)>
193 video_label_is_included_in_audio_label = base::BindRepeating(
194 [](const std::string& video_label,
195 const blink::WebMediaDeviceInfo& audio_info) {
196 return audio_info.label.find(video_label) != std::string::npos;
198 std::cref(video_label));
200 const bool video_has_usb_model = LabelHasUSBModel(video_info.label);
201 std::string video_usb_model = video_has_usb_model
202 ? GetUSBModelFromLabel(video_info.label)
204 base::RepeatingCallback<bool(const blink::WebMediaDeviceInfo&)>
205 usb_model_matches = base::BindRepeating(
206 [](bool video_has_usb_model, const std::string& video_usb_model,
207 const blink::WebMediaDeviceInfo& audio_info) {
208 return video_has_usb_model && LabelHasUSBModel(audio_info.label)
210 GetUSBModelFromLabel(audio_info.label)
213 video_has_usb_model, std::cref(video_usb_model));
215 for (auto* callback :
216 {&video_label_is_included_in_audio_label, &usb_model_matches}) {
217 // The label for the default and communication audio devices may contain the
218 // same label as the real devices, so they should be ignored when trying to
219 // find unique matches.
220 auto real_device_matches =
221 [callback](const blink::WebMediaDeviceInfo& audio_info) {
222 return IsRealAudioDeviceID(audio_info.device_id) &&
223 (*callback).Run(audio_info);
225 auto it_first = base::ranges::find_if(audio_infos, real_device_matches);
226 if (it_first == audio_infos.end())
230 bool duplicate_found = false;
231 while ((it = std::find_if(it + 1, audio_infos.end(),
232 real_device_matches)) != audio_infos.end()) {
233 // If there is more than one match, it is impossible to know which group
234 // ID is the correct one. This may occur if multiple devices of the same
235 // model are installed.
236 if (it->group_id != it_first->group_id) {
237 duplicate_found = true;
242 if (!duplicate_found)
243 return it_first->group_id;
246 return video_info.device_id;
249 struct MediaDevicesManager::EnumerationRequest {
250 EnumerationRequest(const BoolDeviceTypes& requested_types,
251 EnumerationCallback callback)
252 : callback(std::move(callback)) {
253 requested = requested_types;
254 has_seen_result.fill(false);
257 BoolDeviceTypes requested;
258 BoolDeviceTypes has_seen_result;
259 EnumerationCallback callback;
262 // This class helps manage the consistency of cached enumeration results.
263 // It uses a sequence number for each invalidation and enumeration.
264 // A cache is considered valid if the sequence number for the last enumeration
265 // is greater than the sequence number for the last invalidation.
266 // The advantage of using invalidations over directly issuing enumerations upon
267 // each system notification is that some platforms issue multiple notifications
268 // on each device change. The cost of performing multiple redundant
269 // invalidations is significantly lower than the cost of issuing multiple
270 // redundant enumerations.
271 class MediaDevicesManager::CacheInfo {
274 : current_event_sequence_(0),
276 seq_last_invalidation_(0),
277 is_update_ongoing_(false) {}
279 void InvalidateCache() {
280 DCHECK(thread_checker_.CalledOnValidThread());
281 seq_last_invalidation_ = NewEventSequence();
284 bool IsLastUpdateValid() const {
285 DCHECK(thread_checker_.CalledOnValidThread());
286 return seq_last_update_ > seq_last_invalidation_ && !is_update_ongoing_;
289 void UpdateStarted() {
290 DCHECK(thread_checker_.CalledOnValidThread());
291 DCHECK(!is_update_ongoing_);
292 seq_last_update_ = NewEventSequence();
293 is_update_ongoing_ = true;
296 void UpdateCompleted() {
297 DCHECK(thread_checker_.CalledOnValidThread());
298 DCHECK(is_update_ongoing_);
299 is_update_ongoing_ = false;
302 bool is_update_ongoing() const {
303 DCHECK(thread_checker_.CalledOnValidThread());
304 return is_update_ongoing_;
308 int64_t NewEventSequence() {
309 DCHECK(thread_checker_.CalledOnValidThread());
310 return ++current_event_sequence_;
313 int64_t current_event_sequence_;
314 int64_t seq_last_update_;
315 int64_t seq_last_invalidation_;
316 bool is_update_ongoing_;
317 base::ThreadChecker thread_checker_;
320 MediaDevicesManager::SubscriptionRequest::SubscriptionRequest(
321 int render_process_id,
323 const BoolDeviceTypes& subscribe_types,
324 mojo::Remote<blink::mojom::MediaDevicesListener> listener)
325 : render_process_id(render_process_id),
326 render_frame_id(render_frame_id),
327 subscribe_types(subscribe_types),
328 listener_(std::move(listener)) {}
330 MediaDevicesManager::SubscriptionRequest::SubscriptionRequest(
331 SubscriptionRequest&&) = default;
333 MediaDevicesManager::SubscriptionRequest::~SubscriptionRequest() = default;
335 MediaDevicesManager::SubscriptionRequest&
336 MediaDevicesManager::SubscriptionRequest::operator=(SubscriptionRequest&&) =
339 class MediaDevicesManager::AudioServiceDeviceListener
340 : public audio::mojom::DeviceListener {
342 AudioServiceDeviceListener() { ConnectToService(); }
344 AudioServiceDeviceListener(const AudioServiceDeviceListener&) = delete;
345 AudioServiceDeviceListener& operator=(const AudioServiceDeviceListener&) =
348 ~AudioServiceDeviceListener() override = default;
350 void DevicesChanged() override {
351 auto* system_monitor = base::SystemMonitor::Get();
353 system_monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO);
357 void ConnectToService() {
358 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
359 DCHECK(!mojo_audio_device_notifier_);
360 DCHECK(!receiver_.is_bound());
361 GetUIThreadTaskRunner({})->PostTask(
364 &BindDeviceNotifierFromUIThread,
365 mojo_audio_device_notifier_.BindNewPipeAndPassReceiver()));
366 mojo_audio_device_notifier_.set_disconnect_handler(base::BindOnce(
367 &MediaDevicesManager::AudioServiceDeviceListener::OnConnectionError,
368 weak_factory_.GetWeakPtr()));
369 mojo_audio_device_notifier_->RegisterListener(
370 receiver_.BindNewPipeAndPassRemote());
373 void OnConnectionError() {
374 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
375 mojo_audio_device_notifier_.reset();
378 // Resetting the error handler in a posted task since doing it synchronously
379 // results in a browser crash. See https://crbug.com/845142.
380 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
381 FROM_HERE, base::BindOnce(&AudioServiceDeviceListener::ConnectToService,
382 weak_factory_.GetWeakPtr()));
385 mojo::Receiver<audio::mojom::DeviceListener> receiver_{this};
386 mojo::Remote<audio::mojom::DeviceNotifier> mojo_audio_device_notifier_;
388 SEQUENCE_CHECKER(sequence_checker_);
390 base::WeakPtrFactory<AudioServiceDeviceListener> weak_factory_{this};
393 MediaDevicesManager::MediaDevicesManager(
394 media::AudioSystem* audio_system,
395 const scoped_refptr<VideoCaptureManager>& video_capture_manager,
396 StopRemovedInputDeviceCallback stop_removed_input_device_cb,
397 UIInputDeviceChangeCallback ui_input_device_change_cb)
398 : use_fake_devices_(base::CommandLine::ForCurrentProcess()->HasSwitch(
399 switches::kUseFakeDeviceForMediaStream)),
400 audio_system_(audio_system),
401 video_capture_manager_(video_capture_manager),
402 stop_removed_input_device_cb_(std::move(stop_removed_input_device_cb)),
403 ui_input_device_change_cb_(std::move(ui_input_device_change_cb)),
404 permission_checker_(std::make_unique<MediaDevicesPermissionChecker>()),
405 cache_infos_(static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes)),
406 monitoring_started_(false),
407 get_salt_and_origin_cb_(
408 base::BindRepeating(&GetMediaDeviceSaltAndOrigin)) {
409 DCHECK_CURRENTLY_ON(BrowserThread::IO);
410 DCHECK(audio_system_);
411 DCHECK(video_capture_manager_.get());
412 DCHECK(!stop_removed_input_device_cb_.is_null());
413 DCHECK(!ui_input_device_change_cb_.is_null());
414 SendLogMessage("MediaDevicesManager()");
415 cache_policies_.fill(CachePolicy::NO_CACHE);
416 has_seen_result_.fill(false);
419 MediaDevicesManager::~MediaDevicesManager() {
420 DCHECK_CURRENTLY_ON(BrowserThread::IO);
423 void MediaDevicesManager::EnumerateDevices(
424 const BoolDeviceTypes& requested_types,
425 EnumerationCallback callback) {
426 DCHECK_CURRENTLY_ON(BrowserThread::IO);
429 requests_.emplace_back(requested_types, std::move(callback));
430 bool all_results_cached = true;
432 i < static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes); ++i) {
433 if (requested_types[i] && cache_policies_[i] == CachePolicy::NO_CACHE) {
434 all_results_cached = false;
435 DoEnumerateDevices(static_cast<MediaDeviceType>(i));
439 if (all_results_cached)
443 void MediaDevicesManager::EnumerateDevices(
444 int render_process_id,
446 const BoolDeviceTypes& requested_types,
447 bool request_video_input_capabilities,
448 bool request_audio_input_capabilities,
449 EnumerateDevicesCallback callback) {
450 DCHECK_CURRENTLY_ON(BrowserThread::IO);
451 DCHECK(request_video_input_capabilities &&
452 requested_types[static_cast<size_t>(
453 MediaDeviceType::kMediaVideoInput)] ||
454 !request_video_input_capabilities);
455 DCHECK(request_audio_input_capabilities &&
456 requested_types[static_cast<size_t>(
457 MediaDeviceType::kMediaAudioInput)] ||
458 !request_audio_input_capabilities);
459 SendLogMessage(base::StringPrintf(
460 "EnumerateDevices({render_process_id=%d}, {render_frame_id=%d}, "
461 "{request_audio=%s}, {request_video=%s})",
462 render_process_id, render_frame_id,
463 request_audio_input_capabilities ? "true" : "false",
464 request_video_input_capabilities ? "true" : "false"));
466 GetUIThreadTaskRunner({})->PostTask(
469 get_salt_and_origin_cb_,
470 GlobalRenderFrameHostId(render_process_id, render_frame_id),
471 base::BindPostTaskToCurrentDefault(base::BindOnce(
472 &MediaDevicesManager::CheckPermissionsForEnumerateDevices,
473 weak_factory_.GetWeakPtr(), render_process_id, render_frame_id,
474 requested_types, request_video_input_capabilities,
475 request_audio_input_capabilities, std::move(callback)))));
478 #if BUILDFLAG(IS_TIZEN_TV)
479 void MediaDevicesManager::GetMediaDeviceList(EnumerationCallback cb) {
480 DCHECK_CURRENTLY_ON(BrowserThread::IO);
481 enum_cb_ = std::move(cb);
482 got_result_.fill(false);
483 for (auto& device : device_infos_)
487 i < static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes); ++i) {
488 DoEnumerateDevices(static_cast<MediaDeviceType>(i));
493 uint32_t MediaDevicesManager::SubscribeDeviceChangeNotifications(
494 int render_process_id,
496 const BoolDeviceTypes& subscribe_types,
497 mojo::PendingRemote<blink::mojom::MediaDevicesListener> listener) {
498 DCHECK_CURRENTLY_ON(BrowserThread::IO);
500 uint32_t subscription_id = ++last_subscription_id_;
501 mojo::Remote<blink::mojom::MediaDevicesListener> media_devices_listener;
502 media_devices_listener.Bind(std::move(listener));
503 media_devices_listener.set_disconnect_handler(
504 base::BindOnce(&MediaDevicesManager::UnsubscribeDeviceChangeNotifications,
505 weak_factory_.GetWeakPtr(), subscription_id));
506 subscriptions_.emplace(
508 SubscriptionRequest(render_process_id, render_frame_id, subscribe_types,
509 std::move(media_devices_listener)));
511 // Fetch the first device_id_salt for this subscriber's frame, to be able to
512 // later detect changes.
513 GetUIThreadTaskRunner({})->PostTask(
516 get_salt_and_origin_cb_,
517 GlobalRenderFrameHostId(render_process_id, render_frame_id),
518 base::BindPostTaskToCurrentDefault(base::BindOnce(
519 &MediaDevicesManager::SetSubscriptionLastSeenDeviceIdSalt,
520 weak_factory_.GetWeakPtr(), subscription_id))));
522 return subscription_id;
525 void MediaDevicesManager::SetSubscriptionLastSeenDeviceIdSalt(
526 uint32_t subscription_id,
527 const MediaDeviceSaltAndOrigin& salt_and_origin) {
528 auto it = subscriptions_.find(subscription_id);
530 if (it == subscriptions_.end())
532 SubscriptionRequest& request = it->second;
534 request.last_seen_device_id_salt_ = salt_and_origin.device_id_salt();
537 void MediaDevicesManager::UnsubscribeDeviceChangeNotifications(
538 uint32_t subscription_id) {
539 DCHECK_CURRENTLY_ON(BrowserThread::IO);
540 subscriptions_.erase(subscription_id);
543 void MediaDevicesManager::SetCachePolicy(MediaDeviceType type,
544 CachePolicy policy) {
545 DCHECK_CURRENTLY_ON(BrowserThread::IO);
546 DCHECK(blink::IsValidMediaDeviceType(type));
547 if (cache_policies_[static_cast<size_t>(type)] == policy)
550 cache_policies_[static_cast<size_t>(type)] = policy;
551 // If the new policy is SYSTEM_MONITOR, issue an enumeration to populate the
553 if (policy == CachePolicy::SYSTEM_MONITOR) {
554 cache_infos_[static_cast<size_t>(type)].InvalidateCache();
555 DoEnumerateDevices(type);
559 void MediaDevicesManager::StartMonitoring() {
560 DCHECK_CURRENTLY_ON(BrowserThread::IO);
561 if (monitoring_started_)
564 if (!base::SystemMonitor::Get())
567 #if BUILDFLAG(IS_MAC)
568 if (!base::FeatureList::IsEnabled(features::kDeviceMonitorMac))
572 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
573 if (base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess)) {
574 DCHECK(!audio_service_device_listener_);
575 audio_service_device_listener_ =
576 std::make_unique<AudioServiceDeviceListener>();
579 SendLogMessage("StartMonitoring()");
580 monitoring_started_ = true;
581 base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
583 if (base::FeatureList::IsEnabled(features::kMediaDevicesSystemMonitorCache)) {
585 i < static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes); ++i) {
586 DCHECK(cache_policies_[i] != CachePolicy::SYSTEM_MONITOR);
587 SetCachePolicy(static_cast<MediaDeviceType>(i),
588 CachePolicy::SYSTEM_MONITOR);
592 #if BUILDFLAG(IS_MAC)
593 GetUIThreadTaskRunner({})->PostTask(
594 FROM_HERE, base::BindOnce(&MediaDevicesManager::StartMonitoringOnUIThread,
595 base::Unretained(this)));
599 #if BUILDFLAG(IS_MAC)
600 void MediaDevicesManager::StartMonitoringOnUIThread() {
601 DCHECK_CURRENTLY_ON(BrowserThread::UI);
602 BrowserMainLoop* browser_main_loop = content::BrowserMainLoop::GetInstance();
603 if (!browser_main_loop)
605 browser_main_loop->device_monitor_mac()->StartMonitoring();
609 void MediaDevicesManager::StopMonitoring() {
610 DCHECK_CURRENTLY_ON(BrowserThread::IO);
611 if (!monitoring_started_)
613 SendLogMessage(base::StringPrintf("StopMonitoring([this=%p])", this));
614 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
615 audio_service_device_listener_.reset();
616 monitoring_started_ = false;
618 i < static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes); ++i) {
619 SetCachePolicy(static_cast<MediaDeviceType>(i), CachePolicy::NO_CACHE);
623 void MediaDevicesManager::OnDevicesChanged(
624 base::SystemMonitor::DeviceType device_type) {
625 DCHECK_CURRENTLY_ON(BrowserThread::IO);
626 switch (device_type) {
627 case base::SystemMonitor::DEVTYPE_AUDIO:
628 HandleDevicesChanged(MediaDeviceType::kMediaAudioInput);
629 HandleDevicesChanged(MediaDeviceType::kMediaAudioOuput);
631 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
632 HandleDevicesChanged(MediaDeviceType::kMediaVideoInput);
635 break; // Uninteresting device change.
639 media::VideoCaptureFormats MediaDevicesManager::GetVideoInputFormats(
640 const std::string& device_id,
641 bool try_in_use_first) {
642 DCHECK_CURRENTLY_ON(BrowserThread::IO);
643 media::VideoCaptureFormats formats;
645 if (try_in_use_first) {
646 absl::optional<media::VideoCaptureFormat> format =
647 video_capture_manager_->GetDeviceFormatInUse(
648 blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, device_id);
649 if (format.has_value()) {
650 formats.push_back(format.value());
651 ReplaceInvalidFrameRatesWithFallback(&formats);
656 video_capture_manager_->GetDeviceSupportedFormats(device_id, &formats);
657 ReplaceInvalidFrameRatesWithFallback(&formats);
658 // Remove formats that have zero resolution.
659 base::EraseIf(formats, [](const media::VideoCaptureFormat& format) {
660 return format.frame_size.GetArea() <= 0;
663 // If the device does not report any valid format, use a fallback list of
665 if (formats.empty()) {
666 for (const auto& resolution : kFallbackVideoResolutions) {
667 for (const auto frame_rate : kFallbackVideoFrameRates) {
668 formats.push_back(media::VideoCaptureFormat(
669 gfx::Size(resolution.width, resolution.height), frame_rate,
670 media::PIXEL_FORMAT_I420));
678 blink::WebMediaDeviceInfoArray MediaDevicesManager::GetCachedDeviceInfo(
679 MediaDeviceType type) const {
680 DCHECK_CURRENTLY_ON(BrowserThread::IO);
681 return current_snapshot_[static_cast<size_t>(type)];
684 void MediaDevicesManager::RegisterDispatcherHost(
685 std::unique_ptr<blink::mojom::MediaDevicesDispatcherHost> dispatcher_host,
686 mojo::PendingReceiver<blink::mojom::MediaDevicesDispatcherHost> receiver) {
687 DCHECK_CURRENTLY_ON(BrowserThread::IO);
688 dispatcher_hosts_.Add(std::move(dispatcher_host), std::move(receiver));
691 void MediaDevicesManager::SetPermissionChecker(
692 std::unique_ptr<MediaDevicesPermissionChecker> permission_checker) {
693 DCHECK_CURRENTLY_ON(BrowserThread::IO);
694 DCHECK(permission_checker);
695 permission_checker_ = std::move(permission_checker);
698 void MediaDevicesManager::CheckPermissionsForEnumerateDevices(
699 int render_process_id,
701 const BoolDeviceTypes& requested_types,
702 bool request_video_input_capabilities,
703 bool request_audio_input_capabilities,
704 EnumerateDevicesCallback callback,
705 const MediaDeviceSaltAndOrigin& salt_and_origin) {
706 DCHECK_CURRENTLY_ON(BrowserThread::IO);
707 permission_checker_->CheckPermissions(
708 requested_types, render_process_id, render_frame_id,
709 base::BindOnce(&MediaDevicesManager::OnPermissionsCheckDone,
710 weak_factory_.GetWeakPtr(), requested_types,
711 request_video_input_capabilities,
712 request_audio_input_capabilities, std::move(callback),
713 std::move(salt_and_origin)));
716 void MediaDevicesManager::OnPermissionsCheckDone(
717 const MediaDevicesManager::BoolDeviceTypes& requested_types,
718 bool request_video_input_capabilities,
719 bool request_audio_input_capabilities,
720 EnumerateDevicesCallback callback,
721 const MediaDeviceSaltAndOrigin& salt_and_origin,
722 const MediaDevicesManager::BoolDeviceTypes& has_permissions) {
723 DCHECK_CURRENTLY_ON(BrowserThread::IO);
724 // The video-capture subsystem currently does not support group IDs.
725 // If video input devices are requested, also request audio input devices in
726 // order to be able to use an heuristic that guesses group IDs for video
727 // devices by finding matches in audio input devices.
728 // TODO(crbug.com/627793): Remove |internal_requested_types| and use
729 // |requested_types| directly when video capture supports group IDs.
730 BoolDeviceTypes internal_requested_types;
731 internal_requested_types[static_cast<size_t>(
732 MediaDeviceType::kMediaAudioInput)] =
733 requested_types[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)] ||
734 requested_types[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)];
735 internal_requested_types[static_cast<size_t>(
736 MediaDeviceType::kMediaVideoInput)] =
737 requested_types[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)];
738 internal_requested_types[static_cast<size_t>(
739 MediaDeviceType::kMediaAudioOuput)] =
740 requested_types[static_cast<size_t>(MediaDeviceType::kMediaAudioOuput)];
743 internal_requested_types,
744 base::BindOnce(&MediaDevicesManager::OnDevicesEnumerated,
745 weak_factory_.GetWeakPtr(), requested_types,
746 request_video_input_capabilities,
747 request_audio_input_capabilities, std::move(callback),
748 std::move(salt_and_origin), has_permissions));
751 void MediaDevicesManager::OnDevicesEnumerated(
752 const MediaDevicesManager::BoolDeviceTypes& requested_types,
753 bool request_video_input_capabilities,
754 bool request_audio_input_capabilities,
755 EnumerateDevicesCallback callback,
756 const MediaDeviceSaltAndOrigin& salt_and_origin,
757 const MediaDevicesManager::BoolDeviceTypes& has_permissions,
758 const MediaDeviceEnumeration& enumeration) {
759 DCHECK_CURRENTLY_ON(BrowserThread::IO);
761 const bool video_input_capabilities_requested =
762 has_permissions[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)] &&
763 request_video_input_capabilities;
764 const bool audio_input_capabilities_requested =
765 has_permissions[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)] &&
766 request_audio_input_capabilities;
768 std::vector<blink::WebMediaDeviceInfoArray> translation(
769 static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes));
771 i < static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes); ++i) {
772 if (!requested_types[i])
775 for (const auto& device_info : enumeration[i]) {
776 if (base::FeatureList::IsEnabled(
777 features::kEnumerateDevicesHideDeviceIDs) &&
778 !has_permissions[i] && !translation[i].empty())
781 translation[i].push_back(TranslateMediaDeviceInfo(
782 has_permissions[i], salt_and_origin, device_info));
786 GetAudioInputCapabilities(video_input_capabilities_requested,
787 audio_input_capabilities_requested,
788 std::move(callback), enumeration, translation);
791 void MediaDevicesManager::GetAudioInputCapabilities(
792 bool request_video_input_capabilities,
793 bool request_audio_input_capabilities,
794 EnumerateDevicesCallback callback,
795 const MediaDeviceEnumeration& raw_enumeration_results,
796 const std::vector<blink::WebMediaDeviceInfoArray>&
797 hashed_enumeration_results) {
798 DCHECK_CURRENTLY_ON(BrowserThread::IO);
800 EnumerationState state;
801 size_t state_id = next_enumeration_state_id_++;
802 state.video_input_capabilities_requested = request_video_input_capabilities;
803 state.audio_input_capabilities_requested = request_audio_input_capabilities;
804 state.completion_cb = std::move(callback);
805 state.raw_enumeration_results = raw_enumeration_results;
806 state.hashed_enumeration_results = hashed_enumeration_results;
807 state.num_pending_audio_input_capabilities =
808 hashed_enumeration_results[static_cast<size_t>(
809 MediaDeviceType::kMediaAudioInput)]
812 if (!state.audio_input_capabilities_requested ||
813 state.num_pending_audio_input_capabilities == 0) {
814 FinalizeDevicesEnumerated(std::move(state));
818 enumeration_states_[state_id] = std::move(state);
819 DCHECK_EQ(raw_enumeration_results[static_cast<size_t>(
820 MediaDeviceType::kMediaAudioInput)]
822 hashed_enumeration_results[static_cast<size_t>(
823 MediaDeviceType::kMediaAudioInput)]
825 std::size_t num_audio_input_devices =
826 raw_enumeration_results[static_cast<size_t>(
827 MediaDeviceType::kMediaAudioInput)]
829 for (std::size_t i = 0; i < num_audio_input_devices; i++) {
830 auto raw_device_info = raw_enumeration_results[static_cast<size_t>(
831 MediaDeviceType::kMediaAudioInput)][i];
832 auto hashed_device_info = hashed_enumeration_results[static_cast<size_t>(
833 MediaDeviceType::kMediaAudioInput)][i];
835 AudioInputDeviceCapabilitiesPtr capabilities =
836 blink::mojom::AudioInputDeviceCapabilities::New();
837 capabilities->device_id = hashed_device_info.device_id;
838 capabilities->parameters =
839 media::AudioParameters::UnavailableDeviceParams();
840 enumeration_states_[state_id].audio_capabilities.push_back(
841 std::move(capabilities));
842 size_t capabilities_index =
843 enumeration_states_[state_id].audio_capabilities.size() - 1;
844 if (use_fake_devices_) {
845 GetIOThreadTaskRunner({})->PostTask(
847 base::BindOnce(&MediaDevicesManager::GotAudioInputCapabilities,
848 weak_factory_.GetWeakPtr(), state_id,
850 media::AudioParameters::UnavailableDeviceParams()));
852 audio_system_->GetInputStreamParameters(
853 raw_device_info.device_id,
854 base::BindOnce(&MediaDevicesManager::GotAudioInputCapabilities,
855 weak_factory_.GetWeakPtr(), state_id,
856 capabilities_index));
861 void MediaDevicesManager::GotAudioInputCapabilities(
863 size_t capabilities_index,
864 const absl::optional<media::AudioParameters>& parameters) {
865 DCHECK_CURRENTLY_ON(BrowserThread::IO);
866 DCHECK(base::Contains(enumeration_states_, state_id));
868 auto& enumeration_state = enumeration_states_[state_id];
869 DCHECK_GT(enumeration_state.num_pending_audio_input_capabilities, 0);
871 AudioInputDeviceCapabilitiesPtr& capabilities =
872 enumeration_state.audio_capabilities[capabilities_index];
874 capabilities->parameters = *parameters;
875 // Data from the |parameters| field is duplicated in the |channels|,
876 // |sample_rate| and |latency| fields due to the lack of availability
877 // of the media::AudioParameters native mojo mapping in blink.
878 // TODO(crbug.com/787252): Remove redundant fields when |parameters|
879 // is accessible from Blink.
880 capabilities->is_valid = parameters->IsValid();
881 capabilities->channels = parameters->channels();
882 capabilities->sample_rate = parameters->sample_rate();
883 capabilities->latency = parameters->GetBufferDuration();
885 DCHECK(capabilities->parameters.IsValid());
887 if (--enumeration_state.num_pending_audio_input_capabilities == 0) {
888 FinalizeDevicesEnumerated(std::move(enumeration_state));
889 enumeration_states_.erase(state_id);
893 void MediaDevicesManager::FinalizeDevicesEnumerated(
894 EnumerationState enumeration_state) {
895 std::move(enumeration_state.completion_cb)
896 .Run(std::move(enumeration_state.hashed_enumeration_results),
897 enumeration_state.video_input_capabilities_requested
898 ? ComputeVideoInputCapabilities(
900 .raw_enumeration_results[static_cast<size_t>(
901 MediaDeviceType::kMediaVideoInput)],
903 .hashed_enumeration_results[static_cast<size_t>(
904 MediaDeviceType::kMediaVideoInput)])
905 : std::vector<VideoInputDeviceCapabilitiesPtr>(),
906 std::move(enumeration_state.audio_capabilities));
909 std::vector<VideoInputDeviceCapabilitiesPtr>
910 MediaDevicesManager::ComputeVideoInputCapabilities(
911 const blink::WebMediaDeviceInfoArray& raw_device_infos,
912 const blink::WebMediaDeviceInfoArray& translated_device_infos) {
913 DCHECK_EQ(raw_device_infos.size(), translated_device_infos.size());
914 std::vector<VideoInputDeviceCapabilitiesPtr> video_input_capabilities;
915 for (size_t i = 0; i < raw_device_infos.size(); ++i) {
916 VideoInputDeviceCapabilitiesPtr capabilities =
917 blink::mojom::VideoInputDeviceCapabilities::New();
918 capabilities->device_id = translated_device_infos[i].device_id;
919 capabilities->formats = GetVideoInputFormats(raw_device_infos[i].device_id,
920 false /* try_in_use_first */);
921 capabilities->facing_mode = translated_device_infos[i].video_facing;
922 video_input_capabilities.push_back(std::move(capabilities));
924 return video_input_capabilities;
927 void MediaDevicesManager::DoEnumerateDevices(MediaDeviceType type) {
928 DCHECK_CURRENTLY_ON(BrowserThread::IO);
929 DCHECK(blink::IsValidMediaDeviceType(type));
930 CacheInfo& cache_info = cache_infos_[static_cast<size_t>(type)];
931 if (cache_info.is_update_ongoing())
933 SendLogMessage(base::StringPrintf("DoEnumerateDevices({type=%s})",
934 DeviceTypeToString(type)));
936 cache_info.UpdateStarted();
938 case MediaDeviceType::kMediaAudioInput:
939 EnumerateAudioDevices(true /* is_input */);
941 case MediaDeviceType::kMediaVideoInput:
942 ReportVideoEnumerationStart();
943 video_capture_manager_->EnumerateDevices(
944 base::BindOnce(&MediaDevicesManager::VideoInputDevicesEnumerated,
945 weak_factory_.GetWeakPtr()));
947 case MediaDeviceType::kMediaAudioOuput:
948 EnumerateAudioDevices(false /* is_input */);
955 void MediaDevicesManager::EnumerateAudioDevices(bool is_input) {
956 DCHECK_CURRENTLY_ON(BrowserThread::IO);
957 MediaDeviceType type = is_input ? MediaDeviceType::kMediaAudioInput
958 : MediaDeviceType::kMediaAudioOuput;
959 if (use_fake_devices_) {
960 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
961 FROM_HERE, base::BindOnce(&MediaDevicesManager::DevicesEnumerated,
962 weak_factory_.GetWeakPtr(), type,
963 GetFakeAudioDevices(is_input)));
967 audio_system_->GetDeviceDescriptions(
968 is_input, base::BindOnce(&MediaDevicesManager::AudioDevicesEnumerated,
969 weak_factory_.GetWeakPtr(), type));
972 void MediaDevicesManager::VideoInputDevicesEnumerated(
973 DeviceEnumerationResult result_code,
974 const media::VideoCaptureDeviceDescriptors& descriptors) {
975 DCHECK_CURRENTLY_ON(BrowserThread::IO);
976 ReportVideoEnumerationResult(result_code);
978 if (result_code != DeviceEnumerationResult::kSuccess) {
979 std::string log_message =
980 base::StringPrintf("VideoInputDevicesEnumerated got error %d",
981 static_cast<int>(result_code));
982 // Log to both WebRTC logs (for feedback reports) and text logs for
983 // manually-collected chrome logs at customers.
984 SendLogMessage(log_message);
985 VLOG(1) << log_message;
986 // TODO(crbug.com/1313822): Propagate this as an error response to the
987 // page and expose in the JS API.
989 blink::WebMediaDeviceInfoArray snapshot;
990 for (const auto& descriptor : descriptors) {
991 snapshot.emplace_back(descriptor);
993 DevicesEnumerated(MediaDeviceType::kMediaVideoInput, snapshot);
996 void MediaDevicesManager::AudioDevicesEnumerated(
997 MediaDeviceType type,
998 media::AudioDeviceDescriptions device_descriptions) {
999 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1001 blink::WebMediaDeviceInfoArray snapshot;
1002 for (const media::AudioDeviceDescription& description : device_descriptions) {
1003 snapshot.emplace_back(
1004 description.unique_id, description.device_name, description.group_id,
1005 media::VideoCaptureControlSupport(), blink::mojom::FacingMode::kNone);
1007 DevicesEnumerated(type, snapshot);
1010 void MediaDevicesManager::DevicesEnumerated(
1011 MediaDeviceType type,
1012 const blink::WebMediaDeviceInfoArray& snapshot) {
1013 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1014 DCHECK(blink::IsValidMediaDeviceType(type));
1015 UpdateSnapshot(type, snapshot);
1016 cache_infos_[static_cast<size_t>(type)].UpdateCompleted();
1017 has_seen_result_[static_cast<size_t>(type)] = true;
1018 SendLogMessage(GetDevicesEnumeratedLogString(type, snapshot));
1020 if (cache_policies_[static_cast<size_t>(type)] == CachePolicy::NO_CACHE) {
1021 for (auto& request : requests_)
1022 request.has_seen_result[static_cast<size_t>(type)] = true;
1025 // Note that IsLastUpdateValid is always true when policy is NO_CACHE.
1026 if (cache_infos_[static_cast<size_t>(type)].IsLastUpdateValid()) {
1029 DoEnumerateDevices(type);
1032 #if BUILDFLAG(IS_TIZEN_TV)
1033 // report device list to webbrowser
1034 if (IsWebBrowser() && !enum_cb_.is_null()) {
1035 got_result_[static_cast<size_t>(type)] = true;
1037 for (const auto& device : snapshot) {
1038 device_infos_[static_cast<size_t>(type)].emplace_back(
1039 device.device_id, device.label, device.group_id);
1042 bool finish = std::all_of(got_result_.begin(), got_result_.end(),
1043 [](const bool result) { return result == true; });
1045 std::move(enum_cb_).Run(device_infos_);
1050 void MediaDevicesManager::UpdateSnapshot(
1051 MediaDeviceType type,
1052 const blink::WebMediaDeviceInfoArray& new_snapshot,
1053 bool ignore_group_id) {
1054 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1055 DCHECK(blink::IsValidMediaDeviceType(type));
1057 bool need_update_device_change_subscribers = false;
1058 blink::WebMediaDeviceInfoArray& old_snapshot =
1059 current_snapshot_[static_cast<size_t>(type)];
1061 if (type == MediaDeviceType::kMediaAudioInput ||
1062 type == MediaDeviceType::kMediaVideoInput) {
1063 MaybeStopRemovedInputDevices(type, new_snapshot);
1066 // Update the cached snapshot and send notifications only if the device list
1068 if (!base::ranges::equal(
1069 new_snapshot, old_snapshot,
1071 ? [](const blink::WebMediaDeviceInfo& lhs,
1072 const blink::WebMediaDeviceInfo& rhs) { return lhs == rhs; }
1073 : EqualDeviceAndGroupID)) {
1074 // Prevent sending notifications until group IDs are updated using
1075 // a heuristic in ProcessRequests().
1076 // TODO(crbug.com/627793): Remove |is_video_with_group_ids| and the
1077 // corresponding checks when the video-capture subsystem supports
1079 bool is_video_with_good_group_ids =
1080 type == MediaDeviceType::kMediaVideoInput &&
1081 (new_snapshot.size() == 0 || !new_snapshot[0].group_id.empty());
1082 if (type == MediaDeviceType::kMediaAudioInput ||
1083 is_video_with_good_group_ids) {
1084 ui_input_device_change_cb_.Run(type, new_snapshot);
1087 // Do not notify device-change subscribers after the first enumeration
1088 // result, since it is not due to an actual device change.
1089 need_update_device_change_subscribers =
1090 has_seen_result_[static_cast<size_t>(type)] &&
1091 (old_snapshot.size() != 0 || new_snapshot.size() != 0) &&
1092 (type != MediaDeviceType::kMediaVideoInput ||
1093 is_video_with_good_group_ids);
1094 current_snapshot_[static_cast<size_t>(type)] = new_snapshot;
1097 // Generate salts for each subscriber even if the device list hasn't changed,
1098 // as we may need to notify them anyway.
1099 for (const auto& subscription : subscriptions_) {
1100 const SubscriptionRequest& request = subscription.second;
1101 if (request.subscribe_types[static_cast<size_t>(type)]) {
1102 GetUIThreadTaskRunner({})->PostTask(
1105 get_salt_and_origin_cb_,
1106 GlobalRenderFrameHostId(request.render_process_id,
1107 request.render_frame_id),
1108 base::BindPostTaskToCurrentDefault(base::BindOnce(
1109 &MediaDevicesManager::OnSaltAndOriginForSubscription,
1110 weak_factory_.GetWeakPtr(), subscription.first,
1111 request.render_process_id, request.render_frame_id, type,
1112 new_snapshot, need_update_device_change_subscribers))));
1117 void MediaDevicesManager::ProcessRequests() {
1118 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1119 // Populate the group ID field for video devices using a heuristic that looks
1120 // for device coincidences with audio input devices.
1121 // TODO(crbug.com/627793): Remove this once the video-capture subsystem
1122 // supports group IDs.
1123 if (has_seen_result_[static_cast<size_t>(
1124 MediaDeviceType::kMediaVideoInput)]) {
1125 blink::WebMediaDeviceInfoArray video_devices =
1126 current_snapshot_[static_cast<size_t>(
1127 MediaDeviceType::kMediaVideoInput)];
1128 for (auto& video_device_info : video_devices) {
1129 video_device_info.group_id =
1130 GuessVideoGroupID(current_snapshot_[static_cast<size_t>(
1131 MediaDeviceType::kMediaAudioInput)],
1134 UpdateSnapshot(MediaDeviceType::kMediaVideoInput, video_devices,
1135 false /* ignore_group_id */);
1138 base::EraseIf(requests_, [this](EnumerationRequest& request) {
1139 if (IsEnumerationRequestReady(request)) {
1140 std::move(request.callback).Run(current_snapshot_);
1147 bool MediaDevicesManager::IsEnumerationRequestReady(
1148 const EnumerationRequest& request_info) {
1149 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1150 bool is_ready = true;
1152 i < static_cast<size_t>(MediaDeviceType::kNumMediaDeviceTypes); ++i) {
1153 if (!request_info.requested[i])
1155 switch (cache_policies_[i]) {
1156 case CachePolicy::SYSTEM_MONITOR:
1157 if (!cache_infos_[i].IsLastUpdateValid())
1160 case CachePolicy::NO_CACHE:
1161 if (!request_info.has_seen_result[i])
1171 void MediaDevicesManager::HandleDevicesChanged(MediaDeviceType type) {
1172 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1173 DCHECK(blink::IsValidMediaDeviceType(type));
1174 if (!cache_infos_[static_cast<size_t>(type)].is_update_ongoing()) {
1175 SendLogMessage(base::StringPrintf("HandleDevicesChanged({type=%s}",
1176 DeviceTypeToString(type)));
1178 cache_infos_[static_cast<size_t>(type)].InvalidateCache();
1179 DoEnumerateDevices(type);
1182 void MediaDevicesManager::MaybeStopRemovedInputDevices(
1183 MediaDeviceType type,
1184 const blink::WebMediaDeviceInfoArray& new_snapshot) {
1185 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1186 DCHECK(type == MediaDeviceType::kMediaAudioInput ||
1187 type == MediaDeviceType::kMediaVideoInput);
1189 std::vector<blink::WebMediaDeviceInfo> removed_audio_devices;
1190 for (const auto& old_device_info :
1191 current_snapshot_[static_cast<size_t>(type)]) {
1192 // If a device was removed, notify the MediaStreamManager to stop all
1193 // streams using that device.
1194 if (!base::Contains(new_snapshot, old_device_info.device_id,
1195 &blink::WebMediaDeviceInfo::device_id)) {
1196 stop_removed_input_device_cb_.Run(type, old_device_info);
1198 if (type == MediaDeviceType::kMediaAudioInput) {
1199 removed_audio_devices.push_back(old_device_info);
1204 // "default" and "communications" audio devices that have been removed,
1205 // require an extra notification. In fact, such audio devices have associated
1206 // virtual audio devices in the snapshot with the special "default" or
1207 // "communications" IDs. The code below implements an heuristic, such that to
1208 // identify if an audio device was default, it checks whether the old
1209 // snapshot contained an audio device with the same group ID and device ID
1210 // matching either "default" or "communications".
1211 // NOTE: ChromeOS is able to seamlessly redirect streams to the new default
1212 // device, hence the event should not be triggered.
1213 #if !BUILDFLAG(IS_CHROMEOS)
1214 for (const auto& removed_audio_device : removed_audio_devices) {
1215 for (const auto& old_device_info :
1216 current_snapshot_[static_cast<size_t>(type)]) {
1217 if (removed_audio_device.group_id == old_device_info.group_id &&
1218 (old_device_info.device_id ==
1219 media::AudioDeviceDescription::kDefaultDeviceId ||
1220 old_device_info.device_id ==
1221 media::AudioDeviceDescription::kCommunicationsDeviceId)) {
1222 stop_removed_input_device_cb_.Run(type, old_device_info);
1226 #endif // !BUILDFLAG(IS_CHROMEOS)
1229 void MediaDevicesManager::OnSaltAndOriginForSubscription(
1230 uint32_t subscription_id,
1231 int render_process_id,
1232 int render_frame_id,
1233 MediaDeviceType type,
1234 const blink::WebMediaDeviceInfoArray& device_infos,
1235 bool devices_changed,
1236 const MediaDeviceSaltAndOrigin& salt_and_origin) {
1237 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1239 auto it = subscriptions_.find(subscription_id);
1240 if (it == subscriptions_.end())
1242 SubscriptionRequest& request = it->second;
1244 // Continue to propagate a change notification if either the actual device
1245 // list has changed, or the device_id_salt has changed.
1247 request.last_seen_device_id_salt_ &&
1248 salt_and_origin.device_id_salt() != request.last_seen_device_id_salt_;
1250 if (devices_changed || salt_reset) {
1251 MediaDevicesManager::CheckPermissionForDeviceChange(
1252 subscription_id, render_process_id, render_frame_id, type, device_infos,
1255 request.last_seen_device_id_salt_ = salt_and_origin.device_id_salt();
1258 void MediaDevicesManager::CheckPermissionForDeviceChange(
1259 uint32_t subscription_id,
1260 int render_process_id,
1261 int render_frame_id,
1262 MediaDeviceType type,
1263 const blink::WebMediaDeviceInfoArray& device_infos,
1264 const MediaDeviceSaltAndOrigin& salt_and_origin) {
1265 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1266 permission_checker_->CheckPermission(
1267 type, render_process_id, render_frame_id,
1268 base::BindOnce(&MediaDevicesManager::NotifyDeviceChange,
1269 weak_factory_.GetWeakPtr(), subscription_id, type,
1270 device_infos, salt_and_origin));
1273 void MediaDevicesManager::NotifyDeviceChange(
1274 uint32_t subscription_id,
1275 MediaDeviceType type,
1276 const blink::WebMediaDeviceInfoArray& device_infos,
1277 const MediaDeviceSaltAndOrigin& salt_and_origin,
1278 bool has_permission) {
1279 DCHECK_CURRENTLY_ON(BrowserThread::IO);
1280 DCHECK(blink::IsValidMediaDeviceType(type));
1281 auto it = subscriptions_.find(subscription_id);
1282 if (it == subscriptions_.end())
1285 base::StringPrintf("NotifyDeviceChange({subscription_id=%u}, {type=%s}",
1286 subscription_id, DeviceTypeToString(type)));
1288 const SubscriptionRequest& request = it->second;
1289 request.listener_->OnDevicesChanged(
1290 type, TranslateMediaDeviceInfoArray(has_permission, salt_and_origin,
1294 MediaDevicesManager::EnumerationState::EnumerationState() = default;
1295 MediaDevicesManager::EnumerationState::EnumerationState(
1296 EnumerationState&& other) = default;
1297 MediaDevicesManager::EnumerationState::~EnumerationState() = default;
1298 MediaDevicesManager::EnumerationState& MediaDevicesManager::EnumerationState::
1299 operator=(EnumerationState&& other) = default;
1301 } // namespace content