| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/media/media_devices_manager.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <functional> |
| #include <map> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/location.h" |
| #include "base/sequence_checker.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "content/browser/media/media_devices_permission_checker.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/browser/renderer_host/media/video_capture_manager.h" |
| #include "content/browser/service_manager/service_manager_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/common/content_features.h" |
| #include "media/audio/audio_device_description.h" |
| #include "media/audio/audio_system.h" |
| #include "media/base/media_switches.h" |
| #include "mojo/public/cpp/bindings/binding.h" |
| #include "services/audio/public/mojom/constants.mojom.h" |
| #include "services/audio/public/mojom/device_notifications.mojom.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "services/service_manager/public/cpp/identity.h" |
| #include "services/service_manager/public/mojom/connector.mojom-shared.h" |
| |
| #if defined(OS_MACOSX) |
| #include "base/bind_helpers.h" |
| #include "base/single_thread_task_runner.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "media/device_monitors/device_monitor_mac.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| // Resolutions used if the source doesn't support capability enumeration. |
| struct { |
| uint16_t width; |
| uint16_t height; |
| } const kFallbackVideoResolutions[] = {{1920, 1080}, {1280, 720}, {960, 720}, |
| {640, 480}, {640, 360}, {320, 240}, |
| {320, 180}}; |
| |
| // Frame rates for sources with no support for capability enumeration. |
| const uint16_t kFallbackVideoFrameRates[] = {30, 60}; |
| |
| // Private helper method to generate a string for the log message that lists the |
| // human readable names of |devices|. |
| std::string GetLogMessageString( |
| blink::MediaDeviceType device_type, |
| const blink::WebMediaDeviceInfoArray& device_infos) { |
| std::string output_string = |
| base::StringPrintf("Getting devices of type %d:\n", device_type); |
| if (device_infos.empty()) |
| return output_string + "No devices found."; |
| for (const auto& device_info : device_infos) |
| output_string += " " + device_info.label + "\n"; |
| return output_string; |
| } |
| |
| blink::WebMediaDeviceInfoArray GetFakeAudioDevices(bool is_input) { |
| blink::WebMediaDeviceInfoArray result; |
| if (is_input) { |
| result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId, |
| "Fake Default Audio Input", |
| "fake_group_audio_input_default"); |
| result.emplace_back("fake_audio_input_1", "Fake Audio Input 1", |
| "fake_group_audio_input_1"); |
| result.emplace_back("fake_audio_input_2", "Fake Audio Input 2", |
| "fake_group_audio_input_2"); |
| } else { |
| result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId, |
| "Fake Default Audio Output", |
| "fake_group_audio_output_default"); |
| result.emplace_back("fake_audio_output_1", "Fake Audio Output 1", |
| "fake_group_audio_output_1"); |
| result.emplace_back("fake_audio_output_2", "Fake Audio Output 2", |
| "fake_group_audio_output_2"); |
| } |
| |
| return result; |
| } |
| |
| std::string VideoLabelWithoutModelID(const std::string& label) { |
| if (label.rfind(")") != label.size() - 1) |
| return label; |
| |
| auto idx = label.rfind(" ("); |
| if (idx == std::string::npos) |
| return label; |
| |
| return label.substr(0, idx - 1); |
| } |
| |
| bool LabelHasUSBModel(const std::string& label) { |
| return label.size() >= 11 && label[label.size() - 11] == '(' && |
| label[label.size() - 6] == ':' && label[label.size() - 1] == ')'; |
| } |
| |
| std::string GetUSBModelFromLabel(const std::string& label) { |
| DCHECK(LabelHasUSBModel(label)); |
| return label.substr(label.size() - 10, 9); |
| } |
| |
| bool IsRealAudioDeviceID(const std::string& device_id) { |
| return !media::AudioDeviceDescription::IsDefaultDevice(device_id) && |
| !media::AudioDeviceDescription::IsCommunicationsDevice(device_id); |
| } |
| |
| static bool EqualDeviceAndGroupID(const blink::WebMediaDeviceInfo& lhs, |
| const blink::WebMediaDeviceInfo& rhs) { |
| return lhs == rhs && lhs.group_id == rhs.group_id; |
| } |
| |
| void ReplaceInvalidFrameRatesWithFallback(media::VideoCaptureFormats* formats) { |
| for (auto& format : *formats) { |
| if (format.frame_rate <= 0) |
| format.frame_rate = kFallbackVideoFrameRates[0]; |
| } |
| } |
| |
| } // namespace |
| |
| std::string GuessVideoGroupID(const blink::WebMediaDeviceInfoArray& audio_infos, |
| const blink::WebMediaDeviceInfo& video_info) { |
| const std::string video_label = VideoLabelWithoutModelID(video_info.label); |
| |
| // If |video_label| is very small, do not guess in order to avoid false |
| // positives. |
| if (video_label.size() <= 3) |
| return video_info.device_id; |
| |
| base::RepeatingCallback<bool(const blink::WebMediaDeviceInfo&)> |
| video_label_is_included_in_audio_label = base::BindRepeating( |
| [](const std::string& video_label, |
| const blink::WebMediaDeviceInfo& audio_info) { |
| return audio_info.label.find(video_label) != std::string::npos; |
| }, |
| std::cref(video_label)); |
| |
| const bool video_has_usb_model = LabelHasUSBModel(video_info.label); |
| std::string video_usb_model = video_has_usb_model |
| ? GetUSBModelFromLabel(video_info.label) |
| : std::string(); |
| base::RepeatingCallback<bool(const blink::WebMediaDeviceInfo&)> |
| usb_model_matches = base::BindRepeating( |
| [](bool video_has_usb_model, const std::string& video_usb_model, |
| const blink::WebMediaDeviceInfo& audio_info) { |
| return video_has_usb_model && LabelHasUSBModel(audio_info.label) |
| ? video_usb_model == |
| GetUSBModelFromLabel(audio_info.label) |
| : false; |
| }, |
| video_has_usb_model, std::cref(video_usb_model)); |
| |
| for (auto* callback : |
| {&video_label_is_included_in_audio_label, &usb_model_matches}) { |
| // The label for the default and communication audio devices may contain the |
| // same label as the real devices, so they should be ignored when trying to |
| // find unique matches. |
| auto real_device_matches = |
| [callback](const blink::WebMediaDeviceInfo& audio_info) { |
| return IsRealAudioDeviceID(audio_info.device_id) && |
| (*callback).Run(audio_info); |
| }; |
| auto it_first = std::find_if(audio_infos.begin(), audio_infos.end(), |
| real_device_matches); |
| if (it_first == audio_infos.end()) |
| continue; |
| |
| auto it = it_first; |
| bool duplicate_found = false; |
| while ((it = std::find_if(it + 1, audio_infos.end(), |
| real_device_matches)) != audio_infos.end()) { |
| // If there is more than one match, it is impossible to know which group |
| // ID is the correct one. This may occur if multiple devices of the same |
| // model are installed. |
| if (it->group_id != it_first->group_id) { |
| duplicate_found = true; |
| break; |
| } |
| } |
| |
| if (!duplicate_found) |
| return it_first->group_id; |
| } |
| |
| return video_info.device_id; |
| } |
| |
| struct MediaDevicesManager::EnumerationRequest { |
| EnumerationRequest(const BoolDeviceTypes& requested_types, |
| EnumerationCallback callback) |
| : callback(std::move(callback)) { |
| requested = requested_types; |
| has_seen_result.fill(false); |
| } |
| |
| BoolDeviceTypes requested; |
| BoolDeviceTypes has_seen_result; |
| EnumerationCallback callback; |
| }; |
| |
| // This class helps manage the consistency of cached enumeration results. |
| // It uses a sequence number for each invalidation and enumeration. |
| // A cache is considered valid if the sequence number for the last enumeration |
| // is greater than the sequence number for the last invalidation. |
| // The advantage of using invalidations over directly issuing enumerations upon |
| // each system notification is that some platforms issue multiple notifications |
| // on each device change. The cost of performing multiple redundant |
| // invalidations is significantly lower than the cost of issuing multiple |
| // redundant enumerations. |
| class MediaDevicesManager::CacheInfo { |
| public: |
| CacheInfo() |
| : current_event_sequence_(0), |
| seq_last_update_(0), |
| seq_last_invalidation_(0), |
| is_update_ongoing_(false) {} |
| |
| void InvalidateCache() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| seq_last_invalidation_ = NewEventSequence(); |
| } |
| |
| bool IsLastUpdateValid() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return seq_last_update_ > seq_last_invalidation_ && !is_update_ongoing_; |
| } |
| |
| void UpdateStarted() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!is_update_ongoing_); |
| seq_last_update_ = NewEventSequence(); |
| is_update_ongoing_ = true; |
| } |
| |
| void UpdateCompleted() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(is_update_ongoing_); |
| is_update_ongoing_ = false; |
| } |
| |
| bool is_update_ongoing() const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return is_update_ongoing_; |
| } |
| |
| private: |
| int64_t NewEventSequence() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return ++current_event_sequence_; |
| } |
| |
| int64_t current_event_sequence_; |
| int64_t seq_last_update_; |
| int64_t seq_last_invalidation_; |
| bool is_update_ongoing_; |
| base::ThreadChecker thread_checker_; |
| }; |
| |
| MediaDevicesManager::SubscriptionRequest::SubscriptionRequest( |
| int render_process_id, |
| int render_frame_id, |
| const BoolDeviceTypes& subscribe_types, |
| blink::mojom::MediaDevicesListenerPtr listener) |
| : render_process_id(render_process_id), |
| render_frame_id(render_frame_id), |
| subscribe_types(subscribe_types), |
| listener(std::move(listener)) {} |
| |
| MediaDevicesManager::SubscriptionRequest::SubscriptionRequest( |
| SubscriptionRequest&&) = default; |
| |
| MediaDevicesManager::SubscriptionRequest::~SubscriptionRequest() = default; |
| |
| MediaDevicesManager::SubscriptionRequest& |
| MediaDevicesManager::SubscriptionRequest::operator=(SubscriptionRequest&&) = |
| default; |
| |
| class MediaDevicesManager::AudioServiceDeviceListener |
| : public audio::mojom::DeviceListener { |
| public: |
| explicit AudioServiceDeviceListener(service_manager::Connector* connector) |
| : binding_(this) { |
| TryConnectToService(connector); |
| } |
| ~AudioServiceDeviceListener() override = default; |
| |
| void DevicesChanged() override { |
| auto* system_monitor = base::SystemMonitor::Get(); |
| if (system_monitor) |
| system_monitor->ProcessDevicesChanged(base::SystemMonitor::DEVTYPE_AUDIO); |
| } |
| |
| private: |
| void TryConnectToService(service_manager::Connector* connector) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Check if the service manager is managing the audio service. |
| // |
| // TODO: Is this necessary? We should know at build how this will respond. |
| connector->QueryService( |
| audio::mojom::kServiceName, |
| base::BindOnce(&AudioServiceDeviceListener::ServiceQueried, |
| weak_factory_.GetWeakPtr(), connector)); |
| } |
| |
| void ServiceQueried(service_manager::Connector* connector, |
| service_manager::mojom::ServiceInfoPtr info) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Do not connect if the service manager is not managing the audio service. |
| if (!info) { |
| LOG(WARNING) << "Audio service not available."; |
| return; |
| } |
| DoConnectToService(connector); |
| } |
| |
| void DoConnectToService(service_manager::Connector* connector) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!mojo_audio_device_notifier_); |
| DCHECK(!binding_); |
| connector->BindInterface(audio::mojom::kServiceName, |
| mojo::MakeRequest(&mojo_audio_device_notifier_)); |
| mojo_audio_device_notifier_.set_connection_error_handler(base::BindOnce( |
| &MediaDevicesManager::AudioServiceDeviceListener::OnConnectionError, |
| weak_factory_.GetWeakPtr(), connector)); |
| audio::mojom::DeviceListenerPtr audio_device_listener_ptr; |
| binding_.Bind(mojo::MakeRequest(&audio_device_listener_ptr)); |
| mojo_audio_device_notifier_->RegisterListener( |
| audio_device_listener_ptr.PassInterface()); |
| } |
| |
| void OnConnectionError(service_manager::Connector* connector) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| mojo_audio_device_notifier_.reset(); |
| binding_.Close(); |
| |
| // Resetting the error handler in a posted task since doing it synchronously |
| // results in a browser crash. See https://crbug.com/845142. |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&AudioServiceDeviceListener::TryConnectToService, |
| weak_factory_.GetWeakPtr(), connector)); |
| } |
| |
| mojo::Binding<audio::mojom::DeviceListener> binding_; |
| audio::mojom::DeviceNotifierPtr mojo_audio_device_notifier_; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| base::WeakPtrFactory<AudioServiceDeviceListener> weak_factory_{this}; |
| |
| DISALLOW_COPY_AND_ASSIGN(AudioServiceDeviceListener); |
| }; |
| |
| MediaDevicesManager::MediaDevicesManager( |
| media::AudioSystem* audio_system, |
| const scoped_refptr<VideoCaptureManager>& video_capture_manager, |
| StopRemovedInputDeviceCallback stop_removed_input_device_cb, |
| UIInputDeviceChangeCallback ui_input_device_change_cb) |
| : use_fake_devices_(base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kUseFakeDeviceForMediaStream)), |
| audio_system_(audio_system), |
| video_capture_manager_(video_capture_manager), |
| stop_removed_input_device_cb_(std::move(stop_removed_input_device_cb)), |
| ui_input_device_change_cb_(std::move(ui_input_device_change_cb)), |
| permission_checker_(std::make_unique<MediaDevicesPermissionChecker>()), |
| cache_infos_(blink::NUM_MEDIA_DEVICE_TYPES), |
| monitoring_started_(false), |
| salt_and_origin_callback_( |
| base::BindRepeating(&GetMediaDeviceSaltAndOrigin)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(audio_system_); |
| DCHECK(video_capture_manager_.get()); |
| DCHECK(!stop_removed_input_device_cb_.is_null()); |
| DCHECK(!ui_input_device_change_cb_.is_null()); |
| cache_policies_.fill(CachePolicy::NO_CACHE); |
| has_seen_result_.fill(false); |
| } |
| |
| MediaDevicesManager::~MediaDevicesManager() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| } |
| |
| void MediaDevicesManager::EnumerateDevices( |
| const BoolDeviceTypes& requested_types, |
| EnumerationCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| StartMonitoring(); |
| |
| requests_.emplace_back(requested_types, std::move(callback)); |
| bool all_results_cached = true; |
| for (size_t i = 0; i < blink::NUM_MEDIA_DEVICE_TYPES; ++i) { |
| if (requested_types[i] && cache_policies_[i] == CachePolicy::NO_CACHE) { |
| all_results_cached = false; |
| DoEnumerateDevices(static_cast<blink::MediaDeviceType>(i)); |
| } |
| } |
| |
| if (all_results_cached) |
| ProcessRequests(); |
| } |
| |
| void MediaDevicesManager::EnumerateDevices( |
| int render_process_id, |
| int render_frame_id, |
| const BoolDeviceTypes& requested_types, |
| bool request_video_input_capabilities, |
| bool request_audio_input_capabilities, |
| EnumerateDevicesCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request_video_input_capabilities && |
| requested_types[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT] || |
| !request_video_input_capabilities); |
| DCHECK(request_audio_input_capabilities && |
| requested_types[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT] || |
| !request_audio_input_capabilities); |
| |
| base::PostTaskAndReplyWithResult( |
| base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}).get(), |
| FROM_HERE, |
| base::BindOnce(salt_and_origin_callback_, render_process_id, |
| render_frame_id), |
| base::BindOnce(&MediaDevicesManager::CheckPermissionsForEnumerateDevices, |
| weak_factory_.GetWeakPtr(), render_process_id, |
| render_frame_id, requested_types, |
| request_video_input_capabilities, |
| request_audio_input_capabilities, std::move(callback))); |
| } |
| |
| uint32_t MediaDevicesManager::SubscribeDeviceChangeNotifications( |
| int render_process_id, |
| int render_frame_id, |
| const BoolDeviceTypes& subscribe_types, |
| blink::mojom::MediaDevicesListenerPtr listener) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| StartMonitoring(); |
| uint32_t subscription_id = ++last_subscription_id_; |
| blink::mojom::MediaDevicesListenerPtr media_devices_listener = |
| std::move(listener); |
| media_devices_listener.set_connection_error_handler( |
| base::BindOnce(&MediaDevicesManager::UnsubscribeDeviceChangeNotifications, |
| weak_factory_.GetWeakPtr(), subscription_id)); |
| subscriptions_.emplace( |
| subscription_id, |
| SubscriptionRequest(render_process_id, render_frame_id, subscribe_types, |
| std::move(media_devices_listener))); |
| |
| return subscription_id; |
| } |
| |
| void MediaDevicesManager::UnsubscribeDeviceChangeNotifications( |
| uint32_t subscription_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| subscriptions_.erase(subscription_id); |
| } |
| |
| void MediaDevicesManager::SetCachePolicy(blink::MediaDeviceType type, |
| CachePolicy policy) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| if (cache_policies_[type] == policy) |
| return; |
| |
| cache_policies_[type] = policy; |
| // If the new policy is SYSTEM_MONITOR, issue an enumeration to populate the |
| // cache. |
| if (policy == CachePolicy::SYSTEM_MONITOR) { |
| cache_infos_[type].InvalidateCache(); |
| DoEnumerateDevices(type); |
| } |
| } |
| |
| void MediaDevicesManager::StartMonitoring() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (monitoring_started_) |
| return; |
| |
| if (!base::SystemMonitor::Get()) |
| return; |
| |
| #if defined(OS_MACOSX) |
| if (!base::FeatureList::IsEnabled(features::kDeviceMonitorMac)) |
| return; |
| #endif |
| |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| if (base::FeatureList::IsEnabled(features::kAudioServiceOutOfProcess)) { |
| DCHECK(!audio_service_device_listener_); |
| if (!connector_) { |
| auto* connector = ServiceManagerContext::GetConnectorForIOThread(); |
| // |connector| can be null on unit tests. |
| if (!connector) |
| return; |
| |
| connector_ = connector->Clone(); |
| } |
| |
| audio_service_device_listener_ = |
| std::make_unique<AudioServiceDeviceListener>(connector_.get()); |
| } |
| #endif |
| monitoring_started_ = true; |
| base::SystemMonitor::Get()->AddDevicesChangedObserver(this); |
| |
| if (base::FeatureList::IsEnabled(features::kMediaDevicesSystemMonitorCache)) { |
| for (size_t i = 0; i < blink::NUM_MEDIA_DEVICE_TYPES; ++i) { |
| DCHECK(cache_policies_[i] != CachePolicy::SYSTEM_MONITOR); |
| SetCachePolicy(static_cast<blink::MediaDeviceType>(i), |
| CachePolicy::SYSTEM_MONITOR); |
| } |
| } |
| |
| #if defined(OS_MACOSX) |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&MediaDevicesManager::StartMonitoringOnUIThread, |
| base::Unretained(this))); |
| #endif |
| } |
| |
| #if defined(OS_MACOSX) |
| void MediaDevicesManager::StartMonitoringOnUIThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| BrowserMainLoop* browser_main_loop = content::BrowserMainLoop::GetInstance(); |
| if (!browser_main_loop) |
| return; |
| browser_main_loop->device_monitor_mac()->StartMonitoring(); |
| } |
| #endif |
| |
| void MediaDevicesManager::StopMonitoring() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!monitoring_started_) |
| return; |
| base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); |
| audio_service_device_listener_.reset(); |
| monitoring_started_ = false; |
| for (size_t i = 0; i < blink::NUM_MEDIA_DEVICE_TYPES; ++i) |
| SetCachePolicy(static_cast<blink::MediaDeviceType>(i), |
| CachePolicy::NO_CACHE); |
| } |
| |
| bool MediaDevicesManager::IsMonitoringStarted() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return monitoring_started_; |
| } |
| |
| void MediaDevicesManager::OnDevicesChanged( |
| base::SystemMonitor::DeviceType device_type) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| switch (device_type) { |
| case base::SystemMonitor::DEVTYPE_AUDIO: |
| HandleDevicesChanged(blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT); |
| HandleDevicesChanged(blink::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT); |
| break; |
| case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: |
| HandleDevicesChanged(blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT); |
| break; |
| default: |
| break; // Uninteresting device change. |
| } |
| } |
| |
| media::VideoCaptureFormats MediaDevicesManager::GetVideoInputFormats( |
| const std::string& device_id, |
| bool try_in_use_first) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| media::VideoCaptureFormats formats; |
| |
| if (try_in_use_first) { |
| base::Optional<media::VideoCaptureFormat> format = |
| video_capture_manager_->GetDeviceFormatInUse( |
| blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE, device_id); |
| if (format.has_value()) { |
| formats.push_back(format.value()); |
| ReplaceInvalidFrameRatesWithFallback(&formats); |
| return formats; |
| } |
| } |
| |
| video_capture_manager_->GetDeviceSupportedFormats(device_id, &formats); |
| ReplaceInvalidFrameRatesWithFallback(&formats); |
| // Remove formats that have zero resolution. |
| base::EraseIf(formats, [](const media::VideoCaptureFormat& format) { |
| return format.frame_size.GetArea() <= 0; |
| }); |
| |
| // If the device does not report any valid format, use a fallback list of |
| // standard formats. |
| if (formats.empty()) { |
| for (const auto& resolution : kFallbackVideoResolutions) { |
| for (const auto frame_rate : kFallbackVideoFrameRates) { |
| formats.push_back(media::VideoCaptureFormat( |
| gfx::Size(resolution.width, resolution.height), frame_rate, |
| media::PIXEL_FORMAT_I420)); |
| } |
| } |
| } |
| |
| return formats; |
| } |
| |
| blink::WebMediaDeviceInfoArray MediaDevicesManager::GetCachedDeviceInfo( |
| blink::MediaDeviceType type) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return current_snapshot_[type]; |
| } |
| |
| MediaDevicesPermissionChecker* |
| MediaDevicesManager::media_devices_permission_checker() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return permission_checker_.get(); |
| } |
| |
| void MediaDevicesManager::SetPermissionChecker( |
| std::unique_ptr<MediaDevicesPermissionChecker> permission_checker) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(permission_checker); |
| permission_checker_ = std::move(permission_checker); |
| } |
| |
| void MediaDevicesManager::CheckPermissionsForEnumerateDevices( |
| int render_process_id, |
| int render_frame_id, |
| const BoolDeviceTypes& requested_types, |
| bool request_video_input_capabilities, |
| bool request_audio_input_capabilities, |
| EnumerateDevicesCallback callback, |
| MediaDeviceSaltAndOrigin salt_and_origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| permission_checker_->CheckPermissions( |
| requested_types, render_process_id, render_frame_id, |
| base::BindOnce(&MediaDevicesManager::OnPermissionsCheckDone, |
| weak_factory_.GetWeakPtr(), requested_types, |
| request_video_input_capabilities, |
| request_audio_input_capabilities, std::move(callback), |
| std::move(salt_and_origin))); |
| } |
| |
| void MediaDevicesManager::OnPermissionsCheckDone( |
| const MediaDevicesManager::BoolDeviceTypes& requested_types, |
| bool request_video_input_capabilities, |
| bool request_audio_input_capabilities, |
| EnumerateDevicesCallback callback, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| const MediaDevicesManager::BoolDeviceTypes& has_permissions) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // The video-capture subsystem currently does not support group IDs. |
| // If video input devices are requested, also request audio input devices in |
| // order to be able to use an heuristic that guesses group IDs for video |
| // devices by finding matches in audio input devices. |
| // TODO(crbug.com/627793): Remove |internal_requested_types| and use |
| // |requested_types| directly when video capture supports group IDs. |
| BoolDeviceTypes internal_requested_types; |
| internal_requested_types[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT] = |
| requested_types[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT] || |
| requested_types[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT]; |
| internal_requested_types[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT] = |
| requested_types[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT]; |
| internal_requested_types[blink::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT] = |
| requested_types[blink::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT]; |
| |
| EnumerateDevices( |
| internal_requested_types, |
| base::BindOnce(&MediaDevicesManager::OnDevicesEnumerated, |
| weak_factory_.GetWeakPtr(), requested_types, |
| request_video_input_capabilities, |
| request_audio_input_capabilities, std::move(callback), |
| std::move(salt_and_origin), has_permissions)); |
| } |
| |
| void MediaDevicesManager::OnDevicesEnumerated( |
| const MediaDevicesManager::BoolDeviceTypes& requested_types, |
| bool request_video_input_capabilities, |
| bool request_audio_input_capabilities, |
| EnumerateDevicesCallback callback, |
| const MediaDeviceSaltAndOrigin& salt_and_origin, |
| const MediaDevicesManager::BoolDeviceTypes& has_permissions, |
| const MediaDeviceEnumeration& enumeration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const bool video_input_capabilities_requested = |
| has_permissions[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT] && |
| request_video_input_capabilities; |
| const bool audio_input_capabilities_requested = |
| has_permissions[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT] && |
| request_audio_input_capabilities; |
| |
| std::vector<blink::WebMediaDeviceInfoArray> translation( |
| blink::NUM_MEDIA_DEVICE_TYPES); |
| for (size_t i = 0; i < blink::NUM_MEDIA_DEVICE_TYPES; ++i) { |
| if (!requested_types[i]) |
| continue; |
| |
| for (const auto& device_info : enumeration[i]) { |
| translation[i].push_back(TranslateMediaDeviceInfo( |
| has_permissions[i], salt_and_origin, device_info)); |
| } |
| } |
| |
| GetAudioInputCapabilities(video_input_capabilities_requested, |
| audio_input_capabilities_requested, |
| std::move(callback), enumeration, translation); |
| } |
| |
| void MediaDevicesManager::GetAudioInputCapabilities( |
| bool request_video_input_capabilities, |
| bool request_audio_input_capabilities, |
| EnumerateDevicesCallback callback, |
| const MediaDeviceEnumeration& raw_enumeration_results, |
| const std::vector<blink::WebMediaDeviceInfoArray>& |
| hashed_enumeration_results) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| EnumerationState state; |
| size_t state_id = next_enumeration_state_id_++; |
| state.video_input_capabilities_requested = request_video_input_capabilities; |
| state.audio_input_capabilities_requested = request_audio_input_capabilities; |
| state.completion_cb = std::move(callback); |
| state.raw_enumeration_results = std::move(raw_enumeration_results); |
| state.hashed_enumeration_results = std::move(hashed_enumeration_results); |
| state.num_pending_audio_input_capabilities = |
| hashed_enumeration_results[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT].size(); |
| |
| if (!state.audio_input_capabilities_requested || |
| state.num_pending_audio_input_capabilities == 0) { |
| FinalizeDevicesEnumerated(std::move(state)); |
| return; |
| } |
| |
| enumeration_states_[state_id] = std::move(state); |
| DCHECK_EQ( |
| raw_enumeration_results[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT].size(), |
| hashed_enumeration_results[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT].size()); |
| std::size_t num_audio_input_devices = |
| raw_enumeration_results[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT].size(); |
| for (std::size_t i = 0; i < num_audio_input_devices; i++) { |
| auto raw_device_info = |
| raw_enumeration_results[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT][i]; |
| auto hashed_device_info = |
| hashed_enumeration_results[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT][i]; |
| |
| AudioInputDeviceCapabilitiesPtr capabilities = |
| blink::mojom::AudioInputDeviceCapabilities::New(); |
| capabilities->device_id = hashed_device_info.device_id; |
| capabilities->parameters = |
| media::AudioParameters::UnavailableDeviceParams(); |
| enumeration_states_[state_id].audio_capabilities.push_back( |
| std::move(capabilities)); |
| size_t capabilities_index = |
| enumeration_states_[state_id].audio_capabilities.size() - 1; |
| if (use_fake_devices_) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&MediaDevicesManager::GotAudioInputCapabilities, |
| weak_factory_.GetWeakPtr(), state_id, |
| capabilities_index, |
| media::AudioParameters::UnavailableDeviceParams())); |
| } else { |
| audio_system_->GetInputStreamParameters( |
| raw_device_info.device_id, |
| base::BindOnce(&MediaDevicesManager::GotAudioInputCapabilities, |
| weak_factory_.GetWeakPtr(), state_id, |
| capabilities_index)); |
| } |
| } |
| } |
| |
| void MediaDevicesManager::GotAudioInputCapabilities( |
| size_t state_id, |
| size_t capabilities_index, |
| const base::Optional<media::AudioParameters>& parameters) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(base::Contains(enumeration_states_, state_id)); |
| |
| auto& enumeration_state = enumeration_states_[state_id]; |
| DCHECK_GT(enumeration_state.num_pending_audio_input_capabilities, 0); |
| |
| AudioInputDeviceCapabilitiesPtr& capabilities = |
| enumeration_state.audio_capabilities[capabilities_index]; |
| if (parameters) { |
| capabilities->parameters = *parameters; |
| // Data from the |parameters| field is duplicated in the |channels|, |
| // |sample_rate| and |latency| fields due to the lack of availability |
| // of the media::AudioParameters native mojo mapping in blink. |
| // TODO(crbug.com/787252): Remove redundant fields when |parameters| |
| // is accessible from Blink. |
| capabilities->is_valid = parameters->IsValid(); |
| capabilities->channels = parameters->channels(); |
| capabilities->sample_rate = parameters->sample_rate(); |
| capabilities->latency = parameters->GetBufferDuration(); |
| } |
| DCHECK(capabilities->parameters.IsValid()); |
| |
| if (--enumeration_state.num_pending_audio_input_capabilities == 0) { |
| FinalizeDevicesEnumerated(std::move(enumeration_state)); |
| enumeration_states_.erase(state_id); |
| } |
| } |
| |
| void MediaDevicesManager::FinalizeDevicesEnumerated( |
| EnumerationState enumeration_state) { |
| std::move(enumeration_state.completion_cb) |
| .Run(std::move(enumeration_state.hashed_enumeration_results), |
| enumeration_state.video_input_capabilities_requested |
| ? ComputeVideoInputCapabilities( |
| enumeration_state.raw_enumeration_results |
| [blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT], |
| enumeration_state.hashed_enumeration_results |
| [blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT]) |
| : std::vector<VideoInputDeviceCapabilitiesPtr>(), |
| std::move(enumeration_state.audio_capabilities)); |
| } |
| |
| std::vector<VideoInputDeviceCapabilitiesPtr> |
| MediaDevicesManager::ComputeVideoInputCapabilities( |
| const blink::WebMediaDeviceInfoArray& raw_device_infos, |
| const blink::WebMediaDeviceInfoArray& translated_device_infos) { |
| DCHECK_EQ(raw_device_infos.size(), translated_device_infos.size()); |
| std::vector<VideoInputDeviceCapabilitiesPtr> video_input_capabilities; |
| for (size_t i = 0; i < raw_device_infos.size(); ++i) { |
| VideoInputDeviceCapabilitiesPtr capabilities = |
| blink::mojom::VideoInputDeviceCapabilities::New(); |
| capabilities->device_id = translated_device_infos[i].device_id; |
| capabilities->formats = GetVideoInputFormats(raw_device_infos[i].device_id, |
| false /* try_in_use_first */); |
| capabilities->facing_mode = translated_device_infos[i].video_facing; |
| video_input_capabilities.push_back(std::move(capabilities)); |
| } |
| return video_input_capabilities; |
| } |
| |
| void MediaDevicesManager::DoEnumerateDevices(blink::MediaDeviceType type) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| CacheInfo& cache_info = cache_infos_[type]; |
| if (cache_info.is_update_ongoing()) |
| return; |
| |
| cache_info.UpdateStarted(); |
| switch (type) { |
| case blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT: |
| EnumerateAudioDevices(true /* is_input */); |
| break; |
| case blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT: |
| video_capture_manager_->EnumerateDevices( |
| base::BindOnce(&MediaDevicesManager::VideoInputDevicesEnumerated, |
| weak_factory_.GetWeakPtr())); |
| break; |
| case blink::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT: |
| EnumerateAudioDevices(false /* is_input */); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void MediaDevicesManager::EnumerateAudioDevices(bool is_input) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| blink::MediaDeviceType type = is_input |
| ? blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT |
| : blink::MEDIA_DEVICE_TYPE_AUDIO_OUTPUT; |
| if (use_fake_devices_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&MediaDevicesManager::DevicesEnumerated, |
| weak_factory_.GetWeakPtr(), type, |
| GetFakeAudioDevices(is_input))); |
| return; |
| } |
| |
| audio_system_->GetDeviceDescriptions( |
| is_input, base::BindOnce(&MediaDevicesManager::AudioDevicesEnumerated, |
| weak_factory_.GetWeakPtr(), type)); |
| } |
| |
| void MediaDevicesManager::VideoInputDevicesEnumerated( |
| const media::VideoCaptureDeviceDescriptors& descriptors) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| blink::WebMediaDeviceInfoArray snapshot; |
| for (const auto& descriptor : descriptors) { |
| snapshot.emplace_back(descriptor); |
| } |
| DevicesEnumerated(blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT, snapshot); |
| } |
| |
| void MediaDevicesManager::AudioDevicesEnumerated( |
| blink::MediaDeviceType type, |
| media::AudioDeviceDescriptions device_descriptions) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| blink::WebMediaDeviceInfoArray snapshot; |
| for (const media::AudioDeviceDescription& description : device_descriptions) { |
| snapshot.emplace_back(description.unique_id, description.device_name, |
| description.group_id, |
| media::VideoFacingMode::MEDIA_VIDEO_FACING_NONE); |
| } |
| DevicesEnumerated(type, snapshot); |
| } |
| |
| void MediaDevicesManager::DevicesEnumerated( |
| blink::MediaDeviceType type, |
| const blink::WebMediaDeviceInfoArray& snapshot) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| UpdateSnapshot(type, snapshot); |
| cache_infos_[type].UpdateCompleted(); |
| has_seen_result_[type] = true; |
| |
| std::string log_message = |
| "New device enumeration result:\n" + GetLogMessageString(type, snapshot); |
| MediaStreamManager::SendMessageToNativeLog(log_message); |
| |
| if (cache_policies_[type] == CachePolicy::NO_CACHE) { |
| for (auto& request : requests_) |
| request.has_seen_result[type] = true; |
| } |
| |
| // Note that IsLastUpdateValid is always true when policy is NO_CACHE. |
| if (cache_infos_[type].IsLastUpdateValid()) { |
| ProcessRequests(); |
| } else { |
| DoEnumerateDevices(type); |
| } |
| } |
| |
| void MediaDevicesManager::UpdateSnapshot( |
| blink::MediaDeviceType type, |
| const blink::WebMediaDeviceInfoArray& new_snapshot, |
| bool ignore_group_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| |
| bool need_update_device_change_subscribers = false; |
| blink::WebMediaDeviceInfoArray& old_snapshot = current_snapshot_[type]; |
| |
| if (type == blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT || |
| type == blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT) { |
| MaybeStopRemovedInputDevices(type, new_snapshot); |
| } |
| |
| // Update the cached snapshot and send notifications only if the device list |
| // has changed. |
| if (old_snapshot.size() != new_snapshot.size() || |
| !std::equal( |
| new_snapshot.begin(), new_snapshot.end(), old_snapshot.begin(), |
| ignore_group_id |
| ? [](const blink::WebMediaDeviceInfo& lhs, |
| const blink::WebMediaDeviceInfo& rhs) { return lhs == rhs; } |
| : EqualDeviceAndGroupID)) { |
| // Prevent sending notifications until group IDs are updated using |
| // a heuristic in ProcessRequests(). |
| // TODO(crbug.com/627793): Remove |is_video_with_group_ids| and the |
| // corresponding checks when the video-capture subsystem supports |
| // group IDs. |
| bool is_video_with_good_group_ids = |
| type == blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT && |
| (new_snapshot.size() == 0 || !new_snapshot[0].group_id.empty()); |
| if (type == blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT || |
| is_video_with_good_group_ids) |
| ui_input_device_change_cb_.Run(type, new_snapshot); |
| |
| // Do not notify device-change subscribers after the first enumeration |
| // result, since it is not due to an actual device change. |
| need_update_device_change_subscribers = |
| has_seen_result_[type] && |
| (old_snapshot.size() != 0 || new_snapshot.size() != 0) && |
| (type != blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT || |
| is_video_with_good_group_ids); |
| current_snapshot_[type] = new_snapshot; |
| } |
| |
| if (need_update_device_change_subscribers) |
| NotifyDeviceChangeSubscribers(type, new_snapshot); |
| } |
| |
| void MediaDevicesManager::ProcessRequests() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Populate the group ID field for video devices using a heuristic that looks |
| // for device coincidences with audio input devices. |
| // TODO(crbug.com/627793): Remove this once the video-capture subsystem |
| // supports group IDs. |
| if (has_seen_result_[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT]) { |
| blink::WebMediaDeviceInfoArray video_devices = |
| current_snapshot_[blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT]; |
| for (auto& video_device_info : video_devices) { |
| video_device_info.group_id = GuessVideoGroupID( |
| current_snapshot_[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT], |
| video_device_info); |
| } |
| UpdateSnapshot(blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT, video_devices, |
| false /* ignore_group_id */); |
| } |
| |
| base::EraseIf(requests_, [this](EnumerationRequest& request) { |
| if (IsEnumerationRequestReady(request)) { |
| std::move(request.callback).Run(current_snapshot_); |
| return true; |
| } |
| return false; |
| }); |
| } |
| |
| bool MediaDevicesManager::IsEnumerationRequestReady( |
| const EnumerationRequest& request_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| bool is_ready = true; |
| for (size_t i = 0; i < blink::NUM_MEDIA_DEVICE_TYPES; ++i) { |
| if (!request_info.requested[i]) |
| continue; |
| switch (cache_policies_[i]) { |
| case CachePolicy::SYSTEM_MONITOR: |
| if (!cache_infos_[i].IsLastUpdateValid()) |
| is_ready = false; |
| break; |
| case CachePolicy::NO_CACHE: |
| if (!request_info.has_seen_result[i]) |
| is_ready = false; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| return is_ready; |
| } |
| |
| void MediaDevicesManager::HandleDevicesChanged(blink::MediaDeviceType type) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| cache_infos_[type].InvalidateCache(); |
| DoEnumerateDevices(type); |
| } |
| |
| void MediaDevicesManager::MaybeStopRemovedInputDevices( |
| blink::MediaDeviceType type, |
| const blink::WebMediaDeviceInfoArray& new_snapshot) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(type == blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT || |
| type == blink::MEDIA_DEVICE_TYPE_VIDEO_INPUT); |
| |
| std::vector<blink::WebMediaDeviceInfo> removed_audio_devices; |
| for (const auto& old_device_info : current_snapshot_[type]) { |
| auto it = |
| std::find_if(new_snapshot.begin(), new_snapshot.end(), |
| [&old_device_info](const blink::WebMediaDeviceInfo& info) { |
| return info.device_id == old_device_info.device_id; |
| }); |
| |
| // If a device was removed, notify the MediaStreamManager to stop all |
| // streams using that device. |
| if (it == new_snapshot.end()) { |
| stop_removed_input_device_cb_.Run(type, old_device_info); |
| |
| if (type == blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT) |
| removed_audio_devices.push_back(old_device_info); |
| } |
| } |
| |
| // "default" and "communications" audio devices that have been removed, |
| // require an extra notification. In fact, such audio devices have associated |
| // virtual audio devices in the snapshot with the special "default" or |
| // "communications" IDs. The code below implements an heuristic, such that to |
| // identify if an audio device was default, it checks whether the old |
| // snapshot contained an audio device with the same group ID and device ID |
| // matching either "default" or "communications". |
| for (const auto& removed_audio_device : removed_audio_devices) { |
| for (const auto& old_device_info : current_snapshot_[type]) { |
| if (removed_audio_device.group_id == old_device_info.group_id && |
| (old_device_info.device_id == |
| media::AudioDeviceDescription::kDefaultDeviceId || |
| old_device_info.device_id == |
| media::AudioDeviceDescription::kCommunicationsDeviceId)) { |
| stop_removed_input_device_cb_.Run(type, old_device_info); |
| } |
| } |
| } |
| } |
| |
| void MediaDevicesManager::NotifyDeviceChangeSubscribers( |
| blink::MediaDeviceType type, |
| const blink::WebMediaDeviceInfoArray& snapshot) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| |
| for (auto& subscription : subscriptions_) { |
| const SubscriptionRequest& request = subscription.second; |
| if (request.subscribe_types[type]) { |
| base::PostTaskAndReplyWithResult( |
| base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::UI}) |
| .get(), |
| FROM_HERE, |
| base::BindOnce(salt_and_origin_callback_, request.render_process_id, |
| request.render_frame_id), |
| base::BindOnce(&MediaDevicesManager::CheckPermissionForDeviceChange, |
| weak_factory_.GetWeakPtr(), subscription.first, |
| request.render_process_id, request.render_frame_id, |
| type, snapshot)); |
| } |
| } |
| } |
| |
| void MediaDevicesManager::CheckPermissionForDeviceChange( |
| uint32_t subscription_id, |
| int render_process_id, |
| int render_frame_id, |
| blink::MediaDeviceType type, |
| const blink::WebMediaDeviceInfoArray& device_infos, |
| MediaDeviceSaltAndOrigin salt_and_origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| permission_checker_->CheckPermission( |
| type, render_process_id, render_frame_id, |
| base::BindOnce(&MediaDevicesManager::NotifyDeviceChange, |
| weak_factory_.GetWeakPtr(), subscription_id, type, |
| device_infos, std::move(salt_and_origin))); |
| } |
| |
| void MediaDevicesManager::NotifyDeviceChange( |
| uint32_t subscription_id, |
| blink::MediaDeviceType type, |
| const blink::WebMediaDeviceInfoArray& device_infos, |
| const MediaDeviceSaltAndOrigin& salt_and_origin, |
| bool has_permission) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsValidMediaDeviceType(type)); |
| auto it = subscriptions_.find(subscription_id); |
| if (it == subscriptions_.end()) |
| return; |
| |
| const SubscriptionRequest& request = it->second; |
| request.listener->OnDevicesChanged( |
| type, TranslateMediaDeviceInfoArray(has_permission, salt_and_origin, |
| device_infos)); |
| } |
| |
| MediaDevicesManager::EnumerationState::EnumerationState() = default; |
| MediaDevicesManager::EnumerationState::EnumerationState( |
| EnumerationState&& other) = default; |
| MediaDevicesManager::EnumerationState::~EnumerationState() = default; |
| MediaDevicesManager::EnumerationState& MediaDevicesManager::EnumerationState:: |
| operator=(EnumerationState&& other) = default; |
| |
| } // namespace content |