| // Copyright 2012 The Chromium Authors |
| // 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_stream_manager.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <list> |
| #include <memory> |
| #include <optional> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/thread.h" |
| #include "base/time/time.h" |
| #include "base/uuid.h" |
| #include "build/build_config.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/gpu/gpu_process_host.h" |
| #include "content/browser/media/capture/desktop_capture_device_uma_types.h" |
| #include "content/browser/media/media_devices_permission_checker.h" |
| #include "content/browser/media/media_devices_util.h" |
| #include "content/browser/permissions/permission_controller_impl.h" |
| #include "content/browser/renderer_host/media/audio_input_device_manager.h" |
| #include "content/browser/renderer_host/media/audio_service_listener.h" |
| #include "content/browser/renderer_host/media/in_process_video_capture_provider.h" |
| #include "content/browser/renderer_host/media/media_capture_devices_impl.h" |
| #include "content/browser/renderer_host/media/media_devices_manager.h" |
| #include "content/browser/renderer_host/media/media_stream_metrics.h" |
| #include "content/browser/renderer_host/media/media_stream_ui_proxy.h" |
| #include "content/browser/renderer_host/media/preferred_audio_output_device_manager.h" |
| #include "content/browser/renderer_host/media/service_video_capture_provider.h" |
| #include "content/browser/renderer_host/media/video_capture_manager.h" |
| #include "content/browser/renderer_host/media/video_capture_provider_switcher.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_process_host_impl.h" |
| #include "content/browser/renderer_host/render_view_host_delegate.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/desktop_media_id.h" |
| #include "content/public/browser/desktop_streams_registry.h" |
| #include "content/public/browser/media_observer.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents_media_capture_id.h" |
| #include "content/public/common/buildflags.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "crypto/hmac.h" |
| #include "media/audio/audio_device_description.h" |
| #include "media/audio/audio_system.h" |
| #include "media/base/audio_parameters.h" |
| #include "media/base/channel_layout.h" |
| #include "media/base/media_switches.h" |
| #include "media/capture/content/screen_enumerator.h" |
| #include "media/capture/video/create_video_capture_device_factory.h" |
| #include "media/capture/video/fake_video_capture_device.h" |
| #include "media/capture/video/fake_video_capture_device_factory.h" |
| #include "media/capture/video/video_capture_system_impl.h" |
| #include "media/mojo/mojom/display_media_information.mojom.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/features_generated.h" |
| #include "third_party/blink/public/common/mediastream/media_devices.h" |
| #include "third_party/blink/public/common/mediastream/media_stream_request.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "third_party/blink/public/common/switches.h" |
| #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/win/scoped_com_initializer.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chromeos/ash/components/audio/cras_audio_handler.h" |
| #include "content/browser/gpu/chromeos/video_capture_dependencies.h" |
| #include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h" |
| #include "media/capture/video/chromeos/jpeg_accelerator_provider.h" |
| #include "media/capture/video/chromeos/public/cros_features.h" |
| #include "media/capture/video/chromeos/system_event_monitor_impl.h" |
| #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h" |
| #endif |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "content/browser/media/captured_surface_controller.h" |
| #endif |
| |
| using ::blink::mojom::MediaDeviceType; |
| |
| namespace content { |
| |
| constinit thread_local MediaStreamManager* media_stream_manager = nullptr; |
| |
| using ::blink::MediaStreamDevice; |
| using ::blink::MediaStreamDevices; |
| using ::blink::MediaStreamRequestType; |
| using ::blink::StreamControls; |
| using ::blink::TrackControls; |
| using ::blink::mojom::CapturedSurfaceControlResult; |
| using ::blink::mojom::GetOpenDeviceResponse; |
| using ::blink::mojom::MediaStreamRequestResult; |
| using ::blink::mojom::MediaStreamType; |
| using ::blink::mojom::StreamSelectionInfo; |
| using ::blink::mojom::StreamSelectionInfoPtr; |
| |
| namespace { |
| // Turns off available audio effects (removes the flag) if the options |
| // explicitly turn them off. |
| void FilterAudioEffects(const StreamControls& controls, int* effects) { |
| DCHECK(effects); |
| // TODO(ajm): Should we handle ECHO_CANCELLER here? |
| } |
| |
| // Unlike other effects, hotword is off by default, so turn it on if it's |
| // requested and available. |
| void EnableHotwordEffect(const StreamControls& controls, int* effects) { |
| DCHECK(effects); |
| if (controls.hotword_enabled) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // Only enable if a hotword device exists. |
| if (ash::CrasAudioHandler::Get()->HasHotwordDevice()) { |
| *effects |= media::AudioParameters::HOTWORD; |
| } |
| #endif |
| } |
| } |
| |
| // Gets raw |device_id| and |group_id| when given a hashed device_id |
| // |hmac_device_id|. Both |device_id| and |group_id| could be null pointers. |
| bool GetDeviceIDAndGroupIDFromHMAC( |
| const MediaDeviceSaltAndOrigin& salt_and_origin, |
| const std::string& hmac_device_id, |
| const blink::WebMediaDeviceInfoArray& devices, |
| std::string* device_id, |
| std::optional<std::string>* group_id) { |
| // The source_id can be empty if the constraint is set but empty. |
| if (hmac_device_id.empty()) { |
| return false; |
| } |
| |
| for (const auto& device_info : devices) { |
| if (!DoesRawMediaDeviceIDMatchHMAC(salt_and_origin, hmac_device_id, |
| device_info.device_id)) { |
| continue; |
| } |
| if (device_id) { |
| *device_id = device_info.device_id; |
| } |
| if (group_id) { |
| *group_id = device_info.group_id.empty() |
| ? std::nullopt |
| : std::make_optional<std::string>(device_info.group_id); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| MediaStreamType ConvertToMediaStreamType(MediaDeviceType type) { |
| switch (type) { |
| case MediaDeviceType::kMediaAudioInput: |
| return MediaStreamType::DEVICE_AUDIO_CAPTURE; |
| case MediaDeviceType::kMediaVideoInput: |
| return MediaStreamType::DEVICE_VIDEO_CAPTURE; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| const char* DeviceTypeToString(MediaDeviceType type) { |
| switch (type) { |
| case MediaDeviceType::kMediaAudioInput: |
| return "DEVICE_AUDIO_INPUT"; |
| case MediaDeviceType::kMediaAudioOutput: |
| return "DEVICE_AUDIO_OUTPUT"; |
| case MediaDeviceType::kMediaVideoInput: |
| return "DEVICE_VIDEO_INPUT"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| const char* RequestTypeToString(blink::MediaStreamRequestType type) { |
| switch (type) { |
| case blink::MEDIA_DEVICE_ACCESS: |
| return "MEDIA_DEVICE_ACCESS"; |
| case blink::MEDIA_DEVICE_UPDATE: |
| return "MEDIA_DEVICE_UPDATE"; |
| case blink::MEDIA_GENERATE_STREAM: |
| return "MEDIA_GENERATE_STREAM"; |
| case blink::MEDIA_GET_OPEN_DEVICE: |
| return "MEDIA_GET_OPEN_DEVICE"; |
| case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: |
| return "MEDIA_OPEN_DEVICE_PEPPER_ONLY"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| const char* StreamTypeToString(blink::mojom::MediaStreamType type) { |
| switch (type) { |
| case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE: |
| return "DEVICE_AUDIO_CAPTURE"; |
| case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE: |
| return "DEVICE_VIDEO_CAPTURE"; |
| case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE: |
| return "GUM_TAB_AUDIO_CAPTURE"; |
| case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE: |
| return "GUM_TAB_VIDEO_CAPTURE"; |
| case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE: |
| return "GUM_DESKTOP_AUDIO_CAPTURE"; |
| case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE: |
| return "GUM_DESKTOP_VIDEO_CAPTURE"; |
| case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE: |
| return "DISPLAY_AUDIO_CAPTURE"; |
| case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE: |
| return "DISPLAY_VIDEO_CAPTURE"; |
| case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB: |
| return "DISPLAY_VIDEO_CAPTURE_THIS_TAB"; |
| case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET: |
| return "DISPLAY_VIDEO_CAPTURE_SET"; |
| case blink::mojom::MediaStreamType::NO_SERVICE: |
| return "NO_SERVICE"; |
| case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES: |
| return "NUM_MEDIA_TYPES"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| const char* RequestStateToString(MediaRequestState state) { |
| switch (state) { |
| case MEDIA_REQUEST_STATE_NOT_REQUESTED: |
| return "STATE_NOT_REQUESTED"; |
| case MEDIA_REQUEST_STATE_REQUESTED: |
| return "STATE_REQUESTED"; |
| case MEDIA_REQUEST_STATE_PENDING_APPROVAL: |
| return "STATE_PENDING_APPROVAL"; |
| case MEDIA_REQUEST_STATE_OPENING: |
| return "STATE_OPENING"; |
| case MEDIA_REQUEST_STATE_DONE: |
| return "STATE_DONE"; |
| case MEDIA_REQUEST_STATE_CLOSING: |
| return "STATE_CLOSING"; |
| case MEDIA_REQUEST_STATE_ERROR: |
| return "STATE_ERROR"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| const char* RequestResultToString( |
| blink::mojom::MediaStreamRequestResult result) { |
| switch (result) { |
| case blink::mojom::MediaStreamRequestResult::OK: |
| return "OK"; |
| case blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED: |
| return "PERMISSION_DENIED"; |
| case blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED: |
| return "PERMISSION_DISMISSED"; |
| case blink::mojom::MediaStreamRequestResult::INVALID_STATE: |
| return "INVALID_STATE"; |
| case blink::mojom::MediaStreamRequestResult::NO_HARDWARE: |
| return "NO_HARDWARE"; |
| case blink::mojom::MediaStreamRequestResult::INVALID_SECURITY_ORIGIN: |
| return "INVALID_SECURITY_ORIGIN"; |
| case blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE: |
| return "INVALID_STATE"; |
| case blink::mojom::MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE: |
| return "TAB_CAPTURE_FAILURE"; |
| case blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE: |
| return "CAPTURE_FAILURE"; |
| case blink::mojom::MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED: |
| return "CONSTRAINT_NOT_SATISFIED"; |
| case blink::mojom::MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO: |
| return "TRACK_START_FAILURE_AUDIO"; |
| case blink::mojom::MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO: |
| return "TRACK_START_FAILURE_VIDEO"; |
| case blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED: |
| return "NOT_SUPPORTED"; |
| case blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN: |
| return "FAILED_DUE_TO_SHUTDOWN"; |
| case blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON: |
| return "KILL_SWITCH_ON"; |
| case blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM: |
| return "PERMISSION_DENIED_BY_SYSTEM"; |
| case blink::mojom::MediaStreamRequestResult::DEVICE_IN_USE: |
| return "DEVICE_IN_USE"; |
| case blink::mojom::MediaStreamRequestResult::REQUEST_CANCELLED: |
| return "REQUEST_CANCELLED"; |
| case blink::mojom::MediaStreamRequestResult::START_TIMEOUT: |
| return "START_TIMEOUT"; |
| case blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_USER: |
| return "PERMISSION_DENIED_BY_USER"; |
| case blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS: |
| break; // Not a valid enum value. |
| } |
| NOTREACHED(); |
| } |
| |
| std::string GetGenerateStreamsLogString( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id) { |
| return base::StringPrintf( |
| "GenerateStreams({render_process_id=%d}, {render_frame_id=%d}, " |
| "{requester_id=%d}, {page_request_id=%d})", |
| render_frame_host_id.child_id, render_frame_host_id.frame_routing_id, |
| requester_id, page_request_id); |
| } |
| |
| std::string GetOpenDeviceLogString(GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| const std::string& device_id, |
| MediaStreamType type) { |
| return base::StringPrintf( |
| "OpenDevice({render_process_id=%d}, {render_frame_id=%d}, " |
| "{requester_id=%d}, {page_request_id=%d}, {device_id=%s}, {type=%s})", |
| render_frame_host_id.child_id, render_frame_host_id.frame_routing_id, |
| requester_id, page_request_id, device_id.c_str(), |
| StreamTypeToString(type)); |
| } |
| |
| std::string GetStopStreamDeviceLogString( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| const std::string& device_id, |
| const base::UnguessableToken& session_id) { |
| return base::StringPrintf( |
| "StopStreamDevice({render_process_id=%d}, {render_frame_id=%d}, " |
| "{requester_id=%d}, {device_id=%s}, {session_id=%s})", |
| render_frame_host_id.child_id, render_frame_host_id.frame_routing_id, |
| requester_id, device_id.c_str(), session_id.ToString().c_str()); |
| } |
| |
| void SendLogMessage(const std::string& message) { |
| MediaStreamManager::SendMessageToNativeLog("MSM::" + message); |
| } |
| |
| void SendVideoCaptureLogMessage(const std::string& message) { |
| MediaStreamManager::SendMessageToNativeLog("video capture: " + message); |
| } |
| |
| // Returns MediaStreamDevices for getDisplayMedia() calls. |
| // Returns a video device built with DesktopMediaID with fake initializers if |
| // |kUseFakeDeviceForMediaStream| is set and |preferred_display_surface| is no |
| // preference. If |exclude_monitor_type_surfaces| is true, returns tab |
| // DesktopMediaID. Otherwise, if |preferred_display_surface| specifies a screen, |
| // window, or tab, returns a video device with matching DesktopMediaID |
| // Returns a video device with default DesktopMediaID otherwise. |
| // Returns an audio device with default device parameters. |
| // If |kUseFakeDeviceForMediaStream| specifies a |
| // browser window, use |render_process_id| and |render_frame_id| as the browser |
| // window identifier. |
| // |
| // When the result of the configuration results in tab-capture, |
| // if `captured_tab_id` is non-null, it represents the tab that |
| // will be captured. Otherwise, the capturer ends up capturing |
| // their own tab. |
| // |
| // TODO(crbug.com/41485487): Refactor this function. |
| MediaStreamDevices DisplayMediaDevicesFromFakeDeviceConfig( |
| blink::mojom::MediaStreamType media_type, |
| bool request_audio, |
| GlobalRenderFrameHostId render_frame_host_id, |
| blink::mojom::PreferredDisplaySurface preferred_display_surface, |
| bool exclude_monitor_type_surfaces, |
| std::optional<WebContentsMediaCaptureId> captured_tab_id) { |
| MediaStreamDevices devices; |
| DesktopMediaID::Type desktop_media_type = DesktopMediaID::TYPE_SCREEN; |
| DesktopMediaID::Id desktop_media_id_id = DesktopMediaID::kNullId; |
| WebContentsMediaCaptureId web_contents_id; |
| media::mojom::DisplayCaptureSurfaceType display_surface = |
| media::mojom::DisplayCaptureSurfaceType::MONITOR; |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| if (command_line && |
| command_line->HasSwitch(switches::kUseFakeDeviceForMediaStream)) { |
| std::vector<media::FakeVideoCaptureDeviceSettings> config; |
| media::FakeVideoCaptureDeviceFactory:: |
| ParseFakeDevicesConfigFromOptionsString( |
| command_line->GetSwitchValueASCII( |
| switches::kUseFakeDeviceForMediaStream), |
| &config); |
| if (!config.empty()) { |
| desktop_media_type = DesktopMediaID::TYPE_NONE; |
| desktop_media_id_id = DesktopMediaID::kFakeId; |
| switch (config[0].display_media_type) { |
| case media::FakeVideoCaptureDevice::DisplayMediaType::ANY: |
| case media::FakeVideoCaptureDevice::DisplayMediaType::MONITOR: |
| desktop_media_type = DesktopMediaID::TYPE_SCREEN; |
| display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR; |
| break; |
| case media::FakeVideoCaptureDevice::DisplayMediaType::WINDOW: |
| desktop_media_type = DesktopMediaID::TYPE_WINDOW; |
| display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW; |
| break; |
| case media::FakeVideoCaptureDevice::DisplayMediaType::BROWSER: |
| desktop_media_type = DesktopMediaID::TYPE_WEB_CONTENTS; |
| display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER; |
| web_contents_id = captured_tab_id.value_or( |
| WebContentsMediaCaptureId{render_frame_host_id.child_id, |
| render_frame_host_id.frame_routing_id}); |
| break; |
| } |
| } |
| } |
| if (exclude_monitor_type_surfaces && |
| desktop_media_type == DesktopMediaID::TYPE_SCREEN) { |
| preferred_display_surface = blink::mojom::PreferredDisplaySurface::BROWSER; |
| } |
| switch (preferred_display_surface) { |
| case blink::mojom::PreferredDisplaySurface::NO_PREFERENCE: |
| break; |
| case blink::mojom::PreferredDisplaySurface::MONITOR: |
| desktop_media_type = DesktopMediaID::TYPE_SCREEN; |
| display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR; |
| break; |
| case blink::mojom::PreferredDisplaySurface::WINDOW: |
| desktop_media_type = DesktopMediaID::TYPE_WINDOW; |
| display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW; |
| break; |
| case blink::mojom::PreferredDisplaySurface::BROWSER: |
| desktop_media_type = DesktopMediaID::TYPE_WEB_CONTENTS; |
| display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER; |
| web_contents_id = captured_tab_id.value_or( |
| WebContentsMediaCaptureId{render_frame_host_id.child_id, |
| render_frame_host_id.frame_routing_id}); |
| break; |
| } |
| DesktopMediaID media_id(desktop_media_type, desktop_media_id_id, |
| web_contents_id); |
| MediaStreamDevice device(media_type, media_id.ToString(), |
| media_id.ToString()); |
| device.display_media_info = media::mojom::DisplayMediaInformation::New( |
| display_surface, /*logical_surface=*/true, |
| media::mojom::CursorCaptureType::NEVER, /*capture_handle=*/nullptr, |
| /*initial_zoom_level=*/100); |
| devices.push_back(device); |
| if (!request_audio) { |
| return devices; |
| } |
| |
| MediaStreamDevice audio_device( |
| MediaStreamType::DISPLAY_AUDIO_CAPTURE, |
| media::AudioDeviceDescription::kDefaultDeviceId, "Fake audio"); |
| audio_device.display_media_info = media::mojom::DisplayMediaInformation::New( |
| display_surface, /*logical_surface=*/true, |
| media::mojom::CursorCaptureType::NEVER, /*capture_handle=*/nullptr, |
| /*initial_zoom_level=*/100); |
| devices.emplace_back(audio_device); |
| return devices; |
| } |
| |
| bool ChangeSourceSupported(const MediaStreamDevices& devices) { |
| for (const MediaStreamDevice& device : devices) { |
| DesktopMediaID media_id = DesktopMediaID::Parse(device.id); |
| if (media_id.type != DesktopMediaID::TYPE_WEB_CONTENTS) { |
| return false; // Change of source only supported between tabs. |
| } |
| } |
| |
| for (const MediaStreamDevice& device : devices) { |
| if (device.type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) { |
| return true; // Established API supporting share-this-tab-instead. |
| } |
| } |
| |
| if (!base::FeatureList::IsEnabled( |
| media::kShareThisTabInsteadButtonGetDisplayMedia)) { |
| return false; // Killswitch engaged. |
| } |
| |
| if (!base::Contains(devices, MediaStreamType::DISPLAY_VIDEO_CAPTURE, |
| &MediaStreamDevice::type) && |
| !base::Contains(devices, MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB, |
| &MediaStreamDevice::type)) { |
| return false; // Not an API call that supports share-this-tab-instead. |
| } |
| |
| if (!base::FeatureList::IsEnabled( |
| media::kShareThisTabInsteadButtonGetDisplayMediaAudio) && |
| base::Contains(devices, MediaStreamType::DISPLAY_AUDIO_CAPTURE, |
| &MediaStreamDevice::type)) { |
| // The user chose to capture audio, but the killswitch against |
| // share-this-tab-instead with audio is engaged. |
| return false; |
| } |
| |
| return true; // getDisplayMedia() and killswitches did not trigger. |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| MediaStreamManager::CapturedSurfaceControllerFactoryCallback |
| MakeDefaultCapturedSurfaceControllerFactory() { |
| return base::BindRepeating( |
| [](GlobalRenderFrameHostId capturer_rfh_id, |
| WebContentsMediaCaptureId captured_wc_id, |
| base::RepeatingCallback<void(int)> on_zoom_level_change_callback) { |
| return std::make_unique<CapturedSurfaceController>( |
| capturer_rfh_id, captured_wc_id, on_zoom_level_change_callback); |
| }); |
| } |
| #endif |
| |
| const blink::MediaStreamDevice* GetStreamDevice( |
| const blink::mojom::StreamDevices& stream_devices, |
| const base::UnguessableToken& session_id) { |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (device.session_id() == session_id) { |
| return &device; |
| } |
| } |
| return nullptr; |
| } |
| |
| bool IsApplicationLoopbackAudioDevice(MediaStreamDevice* device) { |
| return blink::IsAudioInputMediaType(device->type) && |
| media::AudioDeviceDescription::IsApplicationLoopbackDevice(device->id); |
| } |
| |
| } // namespace |
| |
| // MediaStreamManager::DeviceRequest represents a request to either enumerate |
| // available devices or open one or more devices. |
| // TODO(perkj): MediaStreamManager still needs refactoring. I propose we create |
| // several subclasses of DeviceRequest and move some of the responsibility of |
| // the MediaStreamManager to the subclasses to get rid of the way too many if |
| // statements in MediaStreamManager. |
| class MediaStreamManager::DeviceRequest { |
| public: |
| DeviceRequest( |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| bool user_gesture, |
| StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| MediaStreamRequestType request_type, |
| const StreamControls& stream_controls, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| DeviceStoppedCallback device_stopped_callback = DeviceStoppedCallback()) |
| : requesting_render_frame_host_id(requesting_render_frame_host_id), |
| requester_id(requester_id), |
| page_request_id(page_request_id), |
| user_gesture(user_gesture), |
| audio_stream_selection_info_ptr( |
| std::move(audio_stream_selection_info_ptr)), |
| salt_and_origin(std::move(salt_and_origin)), |
| device_stopped_callback(std::move(device_stopped_callback)), |
| should_stop_in_future_( |
| /*size=*/static_cast<size_t>(MediaStreamType::NUM_MEDIA_TYPES), |
| /*value=*/false), |
| state_(/*size=*/static_cast<size_t>(MediaStreamType::NUM_MEDIA_TYPES), |
| /*value=*/MEDIA_REQUEST_STATE_NOT_REQUESTED), |
| devices_opened_count_( |
| /*size=*/static_cast<size_t>(MediaStreamType::NUM_MEDIA_TYPES), |
| /*value=*/0u), |
| transfer_status_map_( |
| /*size=*/static_cast<size_t>(MediaStreamType::NUM_MEDIA_TYPES)), |
| request_type_(request_type), |
| stream_controls_(stream_controls), |
| audio_type_(MediaStreamType::NO_SERVICE), |
| video_type_(MediaStreamType::NO_SERVICE), |
| target_render_frame_host_id_(-1, -1) { |
| SendLogMessage(base::StringPrintf( |
| "DR::DeviceRequest({requesting_process_id=%d}, " |
| "{requesting_frame_id=%d}, {requester_id=%d}, {request_type=%s})", |
| requesting_render_frame_host_id.child_id, |
| requesting_render_frame_host_id.frame_routing_id, requester_id, |
| RequestTypeToString(request_type))); |
| } |
| |
| virtual ~DeviceRequest() = default; |
| |
| void set_request_type(MediaStreamRequestType type) { request_type_ = type; } |
| MediaStreamRequestType request_type() const { return request_type_; } |
| |
| const StreamControls& stream_controls() const { return stream_controls_; } |
| |
| void SetAudioType(MediaStreamType audio_type) { |
| DCHECK(blink::IsAudioInputMediaType(audio_type) || |
| audio_type == MediaStreamType::NO_SERVICE); |
| SendLogMessage(base::StringPrintf( |
| "DR::SetAudioType([requester_id=%d] {audio_type=%s})", requester_id, |
| StreamTypeToString(audio_type))); |
| audio_type_ = audio_type; |
| } |
| |
| MediaStreamType audio_type() const { return audio_type_; } |
| |
| void SetVideoType(MediaStreamType video_type) { |
| DCHECK(blink::IsVideoInputMediaType(video_type) || |
| video_type == MediaStreamType::NO_SERVICE); |
| video_type_ = video_type; |
| } |
| |
| MediaStreamType video_type() const { return video_type_; } |
| |
| void SetAudioRawId(std::string id) { audio_raw_id_ = std::move(id); } |
| const std::optional<std::string>& audio_raw_id() const { |
| return audio_raw_id_; |
| } |
| |
| void SetVideoRawId(std::string id) { video_raw_id_ = std::move(id); } |
| const std::optional<std::string>& video_raw_id() const { |
| return video_raw_id_; |
| } |
| |
| // Creates a MediaStreamRequest object that is used by this request when UI |
| // is asked for permission and device selection. |
| void CreateUIRequest( |
| const std::vector<std::string>& requested_audio_device_ids, |
| const std::vector<std::string>& requested_video_device_ids) { |
| DCHECK(!ui_request_); |
| SendLogMessage(base::StringPrintf( |
| "DR::CreateUIRequest([requester_id=%d] {requested_audio_device_id=%s}, " |
| "{requested_video_device_id=%s})", |
| requester_id, base::JoinString(requested_audio_device_ids, ",").c_str(), |
| base::JoinString(requested_video_device_ids, "").c_str())); |
| target_render_frame_host_id_ = requesting_render_frame_host_id; |
| ui_request_ = std::make_unique<MediaStreamRequest>( |
| requesting_render_frame_host_id.child_id, |
| requesting_render_frame_host_id.frame_routing_id, page_request_id, |
| salt_and_origin.origin(), user_gesture, request_type_, |
| requested_audio_device_ids, requested_video_device_ids, audio_type_, |
| video_type_, stream_controls_.disable_local_echo, |
| stream_controls_.request_pan_tilt_zoom_permission, |
| captured_surface_control_active_); |
| ui_request_->suppress_local_audio_playback = |
| stream_controls_.suppress_local_audio_playback; |
| ui_request_->restrict_own_audio = stream_controls_.restrict_own_audio; |
| ui_request_->exclude_system_audio = stream_controls_.exclude_system_audio; |
| ui_request_->window_audio_preference = |
| stream_controls_.window_audio_preference; |
| ui_request_->exclude_self_browser_surface = |
| stream_controls_.exclude_self_browser_surface; |
| ui_request_->preferred_display_surface = |
| stream_controls_.preferred_display_surface; |
| ui_request_->exclude_monitor_type_surfaces = |
| stream_controls_.exclude_monitor_type_surfaces; |
| } |
| |
| // Creates a tab capture specific MediaStreamRequest object that is used by |
| // this request when UI is asked for permission and device selection. |
| void CreateTabCaptureUIRequest( |
| GlobalRenderFrameHostId target_render_frame_host_id) { |
| DCHECK(!ui_request_); |
| target_render_frame_host_id_ = target_render_frame_host_id; |
| ui_request_ = std::make_unique<MediaStreamRequest>( |
| target_render_frame_host_id_.child_id, |
| target_render_frame_host_id_.frame_routing_id, page_request_id, |
| salt_and_origin.origin(), user_gesture, request_type_, |
| std::vector<std::string>{}, std::vector<std::string>{}, audio_type_, |
| video_type_, stream_controls_.disable_local_echo, |
| /*request_pan_tilt_zoom_permission=*/false, |
| captured_surface_control_active_); |
| ui_request_->exclude_system_audio = stream_controls_.exclude_system_audio; |
| ui_request_->window_audio_preference = |
| stream_controls_.window_audio_preference; |
| } |
| |
| bool HasUIRequest() const { return ui_request_.get() != nullptr; } |
| std::unique_ptr<MediaStreamRequest> DetachUIRequest() { |
| return std::move(ui_request_); |
| } |
| |
| // Update the request state and notify observers. |
| void SetState(MediaStreamType stream_type, MediaRequestState new_state) { |
| SendLogMessage(base::StringPrintf( |
| "DR::SetState([requester_id=%d] {stream_type=%s}, {new_state=%s})", |
| requester_id, StreamTypeToString(stream_type), |
| RequestStateToString(new_state))); |
| |
| if (stream_type == MediaStreamType::NUM_MEDIA_TYPES) { |
| for (int i = static_cast<int>(MediaStreamType::NO_SERVICE) + 1; |
| i < static_cast<int>(MediaStreamType::NUM_MEDIA_TYPES); ++i) { |
| state_[i] = new_state; |
| } |
| } else { |
| state_[static_cast<int>(stream_type)] = new_state; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| NotifyMultiCaptureStateChanged(requesting_render_frame_host_id, new_state); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| MediaObserver* media_observer = |
| GetContentClient()->browser()->GetMediaObserver(); |
| if (!media_observer) { |
| return; |
| } |
| |
| if (stream_type == MediaStreamType::NUM_MEDIA_TYPES) { |
| for (int i = static_cast<int>(MediaStreamType::NO_SERVICE) + 1; |
| i < static_cast<int>(MediaStreamType::NUM_MEDIA_TYPES); ++i) { |
| media_observer->OnMediaRequestStateChanged( |
| target_render_frame_host_id_.child_id, |
| target_render_frame_host_id_.frame_routing_id, page_request_id, |
| salt_and_origin.origin().GetURL(), static_cast<MediaStreamType>(i), |
| new_state); |
| } |
| } else { |
| media_observer->OnMediaRequestStateChanged( |
| target_render_frame_host_id_.child_id, |
| target_render_frame_host_id_.frame_routing_id, page_request_id, |
| salt_and_origin.origin().GetURL(), stream_type, new_state); |
| } |
| } |
| |
| bool ShouldStopInFuture(MediaStreamType stream_type) { |
| return should_stop_in_future_[static_cast<int>(stream_type)]; |
| } |
| |
| void SetShouldStopInFuture(MediaStreamType stream_type, |
| bool should_be_stopped) { |
| should_stop_in_future_[static_cast<int>(stream_type)] = should_be_stopped; |
| } |
| |
| MediaRequestState state(MediaStreamType stream_type) const { |
| return state_[static_cast<int>(stream_type)]; |
| } |
| |
| void ResetDevicesOpened(MediaStreamType stream_type) { |
| devices_opened_count_[static_cast<int>(stream_type)] = 0; |
| } |
| |
| void SetDeviceOpened(MediaStreamType stream_type) { |
| devices_opened_count_[static_cast<int>(stream_type)]++; |
| } |
| |
| size_t devices_opened_count(MediaStreamType stream_type) const { |
| return devices_opened_count_[static_cast<int>(stream_type)]; |
| } |
| |
| std::optional<TransferState> GetTransferState( |
| MediaStreamType stream_type, |
| const base::UnguessableToken& transfer_id) { |
| const auto& transfer_map = |
| transfer_status_map_[static_cast<int>(stream_type)]; |
| auto it = transfer_map.find(transfer_id); |
| if (it == transfer_map.end()) { |
| return std::nullopt; |
| } |
| return it->second.state; |
| } |
| |
| void SetTransferState(MediaStreamType stream_type, |
| const base::UnguessableToken& transfer_id, |
| TransferState transfer_state) { |
| auto& transfer_map = transfer_status_map_[static_cast<int>(stream_type)]; |
| transfer_map[transfer_id] = {transfer_state, |
| /*start_time=*/base::TimeTicks::Now()}; |
| } |
| |
| bool IsTransferMapEmpty(MediaStreamType stream_type) const { |
| return transfer_status_map_[static_cast<int>(stream_type)].empty(); |
| } |
| |
| void RemoveEntryInTransferMap(MediaStreamType stream_type, |
| const base::UnguessableToken& transfer_id) { |
| auto& transfer_map = transfer_status_map_[static_cast<int>(stream_type)]; |
| transfer_map.erase(transfer_id); |
| } |
| |
| void SetCapturingLinkSecured(bool is_secure) { |
| MediaObserver* media_observer = |
| GetContentClient()->browser()->GetMediaObserver(); |
| if (!media_observer) { |
| return; |
| } |
| |
| media_observer->OnSetCapturingLinkSecured( |
| target_render_frame_host_id_.child_id, |
| target_render_frame_host_id_.frame_routing_id, page_request_id, |
| video_type_, is_secure); |
| } |
| |
| // This function checks if the request is for the getAllScreensMedia API. |
| bool IsGetAllScreensMedia() const { |
| return stream_controls_.video.stream_type == |
| blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET; |
| } |
| |
| void SetLabel(const std::string& label) { label_ = label; } |
| |
| void DisableAudioSharing() { |
| SetAudioType(MediaStreamType::NO_SERVICE); |
| stream_controls_.audio.stream_type = MediaStreamType::NO_SERVICE; |
| stream_controls_.hotword_enabled = false; |
| stream_controls_.disable_local_echo = false; |
| stream_controls_.suppress_local_audio_playback = false; |
| stream_controls_.restrict_own_audio = false; |
| stream_controls_.exclude_system_audio = false; |
| stream_controls_.window_audio_preference = |
| blink::mojom::WindowAudioPreference::kExclude; |
| } |
| |
| // TODO(crbug.com/40247147): Remove this method from DeviceRequest when |
| // GenerateStreamRequest::FinalizeRequest and |
| // GetOpenDeviceRequest::FinalizeRequest have been implemented (this should be |
| // an internal callback in those subclasses). |
| virtual void PanTiltZoomPermissionChecked(const std::string& label, |
| bool pan_tilt_zoom_allowed) { |
| NOTREACHED(); |
| } |
| |
| // TODO(crbug.com/40247147): Combine FinalizeRequest and |
| // FinalizeMediaAccessRequest, implement it for the remaining subclasses and |
| // make it into on pure virtual function. |
| virtual void FinalizeRequest(const std::string& label) { NOTREACHED(); } |
| |
| virtual void FinalizeMediaAccessRequest( |
| const std::string& label, |
| const blink::mojom::StreamDevicesSet&) { |
| NOTREACHED(); |
| } |
| |
| virtual void FinalizeRequestFailed(MediaStreamRequestResult result) = 0; |
| |
| virtual void FinalizeChangeDevice(const std::string& label) { NOTREACHED(); } |
| |
| virtual void OnRequestStateChangeFromBrowser( |
| const std::string& label, |
| const DesktopMediaID& media_id, |
| blink::mojom::MediaStreamStateChange new_state) {} |
| |
| virtual void OnCaptureConfigurationChanged( |
| const std::string& label, |
| const blink::MediaStreamDevice& device) {} |
| |
| base::RepeatingCallback<void(const std::string&, |
| blink::mojom::MediaStreamType type, |
| media::mojom::CaptureHandlePtr)> |
| OnCaptureHandleChangeCb() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return base::BindRepeating(&DeviceRequest::OnCaptureHandleChange, |
| GetWeakPtr()); |
| } |
| |
| // Receives a new capture-handle from the CaptureHandleManager. |
| virtual void OnCaptureHandleChange( |
| const std::string& label, |
| blink::mojom::MediaStreamType type, |
| media::mojom::CaptureHandlePtr capture_handle) {} |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| // If capturing a tab, returns the tab's |WebContentsMediaCaptureId|. |
| // Otherwise, returns an empty |WebContentsMediaCaptureId|. |
| WebContentsMediaCaptureId GetCapturedTabId() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| WebContentsMediaCaptureId captured_wc_id; |
| |
| // Tab-capture will always have `size() == 1` here. A size greater than 1 |
| // indicates getAllScreensMedia() - those devices can be skipped. |
| if (stream_devices_set.stream_devices.size() != 1 || |
| !stream_devices_set.stream_devices[0]->video_device.has_value()) { |
| return captured_wc_id; |
| } |
| |
| // Ignore Parse()'s return value. If it fails, `captured_wc_id` is left with |
| // the null ID, which is the value we need to return in that case. |
| WebContentsMediaCaptureId::Parse( |
| stream_devices_set.stream_devices[0]->video_device->id, |
| &captured_wc_id); |
| |
| return captured_wc_id; |
| } |
| |
| CapturedSurfaceController* captured_surface_controller() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return captured_surface_controller_.get(); |
| } |
| |
| void SetCapturedSurfaceController( |
| std::unique_ptr<CapturedSurfaceController> controller) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| CHECK(!captured_surface_controller_); |
| captured_surface_controller_ = std::move(controller); |
| } |
| |
| // If capturing a tab, zoom-level updates are received through this callback. |
| virtual void OnZoomLevelChange(const std::string& label, int zoom_level) {} |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| // Marks that CSC was used at least once during this capture-session. |
| void SetCapturedSurfaceControlActive() { |
| captured_surface_control_active_ = true; |
| } |
| |
| bool captured_surface_control_active() const { |
| return captured_surface_control_active_; |
| } |
| |
| // The render frame host id that requested this stream to be generated and |
| // that will receive a handle to the MediaStream. This may be different from |
| // MediaStreamRequest::render_process_id which in the tab capture case |
| // specifies the target renderer from which audio and video is captured. |
| const GlobalRenderFrameHostId requesting_render_frame_host_id; |
| |
| // The id of the object that requested this stream to be generated and that |
| // will receive a handle to the MediaStream. This may be different from |
| // MediaStreamRequest::requester_id which in the tab capture case |
| // specifies the target renderer from which audio and video is captured. |
| const int requester_id; |
| |
| // An ID the render frame provided to identify this request. |
| const int page_request_id; |
| |
| const bool user_gesture; |
| |
| // Information as of how to select a stream for an audio device provided by |
| // the caller. |
| // NB: This information is invalid after the request has been processed. |
| StreamSelectionInfoPtr audio_stream_selection_info_ptr; |
| |
| const MediaDeviceSaltAndOrigin salt_and_origin; |
| |
| blink::mojom::StreamDevicesSet stream_devices_set; |
| blink::mojom::StreamDevicesSet old_stream_devices_set; |
| |
| DeviceStoppedCallback device_stopped_callback; |
| |
| std::unique_ptr<MediaStreamUIProxy> ui_proxy; |
| |
| std::string tab_capture_device_id; |
| |
| PermissionController::SubscriptionId audio_subscription_id; |
| |
| PermissionController::SubscriptionId video_subscription_id; |
| |
| virtual base::WeakPtr<DeviceRequest> GetWeakPtr() = 0; |
| |
| private: |
| #if BUILDFLAG(IS_CHROMEOS) |
| void NotifyMultiCaptureStateChanged(GlobalRenderFrameHostId frame_host_id, |
| MediaRequestState new_state) { |
| if (!IsGetAllScreensMedia()) { |
| return; |
| } |
| switch (new_state) { |
| case MediaRequestState::MEDIA_REQUEST_STATE_OPENING: |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](GlobalRenderFrameHostId renderer_id, std::string label) { |
| GetContentClient()->browser()->NotifyMultiCaptureStateChanged( |
| renderer_id, label, |
| ContentBrowserClient::MultiCaptureChanged::kStarted); |
| }, |
| frame_host_id, label_)); |
| break; |
| case MediaRequestState::MEDIA_REQUEST_STATE_ERROR: |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](GlobalRenderFrameHostId renderer_id, std::string label) { |
| GetContentClient()->browser()->NotifyMultiCaptureStateChanged( |
| renderer_id, label, |
| ContentBrowserClient::MultiCaptureChanged::kStopped); |
| }, |
| frame_host_id, label_)); |
| break; |
| case MediaRequestState::MEDIA_REQUEST_STATE_CLOSING: |
| case MediaRequestState::MEDIA_REQUEST_STATE_NOT_REQUESTED: |
| case MediaRequestState::MEDIA_REQUEST_STATE_REQUESTED: |
| case MediaRequestState::MEDIA_REQUEST_STATE_PENDING_APPROVAL: |
| case MediaRequestState::MEDIA_REQUEST_STATE_DONE: |
| // Nothing to do as usage indicators only need to shown while the |
| // capture is active. |
| break; |
| } |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| // Mark true if the MediaStreamDevice of |MediaStreamType| type should be |
| // stopped but can't at the moment because of ongoing transfers. |
| std::vector<bool> should_stop_in_future_; |
| std::vector<MediaRequestState> state_; |
| // This vector keeps track of how many devices of a specific |MediaStreamType| |
| // were already opened for this request. |
| std::vector<size_t> devices_opened_count_; |
| std::unique_ptr<MediaStreamRequest> ui_request_; |
| // This vector of map tracks all the ongoing transfers of MediaStreamDevice of |
| // |MediaStreamType| type. |
| std::vector<TransferMap> transfer_status_map_; |
| MediaStreamRequestType request_type_; |
| StreamControls stream_controls_; |
| MediaStreamType audio_type_ = MediaStreamType::NO_SERVICE; |
| std::optional<std::string> audio_raw_id_; |
| MediaStreamType video_type_ = MediaStreamType::NO_SERVICE; |
| std::optional<std::string> video_raw_id_; |
| GlobalRenderFrameHostId target_render_frame_host_id_; |
| std::string label_; |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| std::unique_ptr<CapturedSurfaceController> captured_surface_controller_; |
| #endif |
| bool captured_surface_control_active_ = false; |
| }; |
| |
| class MediaStreamManager::MediaAccessRequest |
| : public MediaStreamManager::DeviceRequest { |
| public: |
| MediaAccessRequest(GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| const StreamControls& controls, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| MediaAccessRequestCallback media_access_request_callback) |
| : DeviceRequest(requesting_render_frame_host_id, |
| requester_id, |
| page_request_id, |
| /*user_gesture=*/false, |
| StreamSelectionInfo::NewSearchOnlyByDeviceId({}), |
| blink::MEDIA_DEVICE_ACCESS, |
| controls, |
| std::move(salt_and_origin)), |
| media_access_request_callback_( |
| std::move(media_access_request_callback)) {} |
| |
| ~MediaAccessRequest() override { DCHECK_CURRENTLY_ON(BrowserThread::IO); } |
| |
| void FinalizeMediaAccessRequest( |
| const std::string& label, |
| const blink::mojom::StreamDevicesSet& stream_devices_set) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(media_access_request_callback_); |
| SendLogMessage(base::StringPrintf( |
| "FinalizeMediaAccessRequest({label=%s}, {requester_id=" |
| "%d}, {request_type=%s})", |
| label.c_str(), requester_id, RequestTypeToString(request_type()))); |
| std::move(media_access_request_callback_) |
| .Run(stream_devices_set, std::move(ui_proxy)); |
| } |
| |
| void FinalizeRequestFailed(MediaStreamRequestResult) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(media_access_request_callback_); |
| std::move(media_access_request_callback_) |
| .Run(/*stream_devices_set=*/blink::mojom::StreamDevicesSet(), |
| std::move(ui_proxy)); |
| } |
| |
| private: |
| base::WeakPtr<DeviceRequest> GetWeakPtr() override { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| // Callback to the requester which audio/video devices have been selected. |
| // It can be null if the requester has no interest to know the result. |
| MediaAccessRequestCallback media_access_request_callback_; |
| base::WeakPtrFactory<DeviceRequest> weak_factory_{this}; |
| }; |
| |
| class MediaStreamManager::CreateDeviceRequest |
| : public MediaStreamManager::DeviceRequest { |
| public: |
| CreateDeviceRequest( |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| bool user_gesture, |
| StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| MediaStreamRequestType request_type, |
| const StreamControls& controls, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| DeviceStoppedCallback device_stopped_callback, |
| DeviceChangedCallback device_changed_callback, |
| DeviceRequestStateChangeCallback device_request_state_change_callback, |
| DeviceCaptureConfigurationChangeCallback |
| device_capture_configuration_change_callback, |
| DeviceCaptureHandleChangeCallback device_capture_handle_change_callback, |
| ZoomLevelChangeCallback zoom_level_change_callback) |
| : DeviceRequest(requesting_render_frame_host_id, |
| requester_id, |
| page_request_id, |
| user_gesture, |
| std::move(audio_stream_selection_info_ptr), |
| request_type, |
| controls, |
| std::move(salt_and_origin), |
| std::move(device_stopped_callback)), |
| device_changed_callback_(std::move(device_changed_callback)), |
| device_request_state_change_callback_( |
| std::move(device_request_state_change_callback)), |
| device_capture_configuration_change_callback_( |
| std::move(device_capture_configuration_change_callback)), |
| device_capture_handle_change_callback_( |
| std::move(device_capture_handle_change_callback)), |
| zoom_level_change_callback_(std::move(zoom_level_change_callback)) {} |
| |
| void FinalizeChangeDevice(const std::string& label) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(device_changed_callback_); |
| DCHECK_EQ(1u, old_stream_devices_set.stream_devices.size()); |
| DCHECK_EQ(1u, stream_devices_set.stream_devices.size()); |
| |
| const blink::mojom::StreamDevices& old_devices = |
| *old_stream_devices_set.stream_devices[0]; |
| const blink::mojom::StreamDevices& new_devices = |
| *stream_devices_set.stream_devices[0]; |
| |
| SendLogMessage(base::StringPrintf( |
| "FinalizeChangeDevice({label=%s}, {requester_id=" |
| "%d}, {request_type=%s})", |
| label.c_str(), requester_id, RequestTypeToString(request_type()))); |
| |
| std::vector<std::vector<MediaStreamDevice>> old_devices_by_type( |
| static_cast<size_t>(MediaStreamType::NUM_MEDIA_TYPES)); |
| for (const std::optional<blink::MediaStreamDevice>* old_device_ptr : |
| {&old_devices.audio_device, &old_devices.video_device}) { |
| if (!old_device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& old_device = old_device_ptr->value(); |
| old_devices_by_type[static_cast<size_t>(old_device.type)].push_back( |
| old_device); |
| } |
| |
| for (const std::optional<blink::MediaStreamDevice>* new_device_ptr : |
| {&new_devices.audio_device, &new_devices.video_device}) { |
| if (!new_device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& new_device = new_device_ptr->value(); |
| MediaStreamDevice old_device; |
| auto& old_devices_of_new_device_type = |
| old_devices_by_type[static_cast<int>(new_device.type)]; |
| if (!old_devices_of_new_device_type.empty()) { |
| old_device = old_devices_of_new_device_type.back(); |
| old_devices_of_new_device_type.pop_back(); |
| } |
| |
| device_changed_callback_.Run(label, old_device, new_device); |
| } |
| |
| for (const auto& old_media_stream_devices : old_devices_by_type) { |
| for (const auto& old_device : old_media_stream_devices) { |
| device_changed_callback_.Run(label, old_device, MediaStreamDevice()); |
| } |
| } |
| } |
| |
| void OnRequestStateChangeFromBrowser( |
| const std::string& label, |
| const DesktopMediaID& media_id, |
| blink::mojom::MediaStreamStateChange new_state) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!device_request_state_change_callback_) { |
| return; |
| } |
| |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (DesktopMediaID::Parse(device.id) == media_id) { |
| device_request_state_change_callback_.Run(label, device, new_state); |
| } |
| } |
| } |
| } |
| |
| void OnCaptureConfigurationChanged( |
| const std::string& label, |
| const blink::MediaStreamDevice& device) override { |
| if (device_capture_configuration_change_callback_) { |
| device_capture_configuration_change_callback_.Run(label, device); |
| } |
| } |
| |
| // Receive a new capture-handle from the CaptureHandleManager. |
| void OnCaptureHandleChange( |
| const std::string& label, |
| blink::mojom::MediaStreamType type, |
| media::mojom::CaptureHandlePtr capture_handle) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK_EQ(1u, stream_devices_set.stream_devices.size()); |
| const blink::mojom::StreamDevices& devices = |
| *stream_devices_set.stream_devices[0]; |
| |
| const MediaStreamDevice* device = nullptr; |
| if (blink::IsAudioInputMediaType(type) && |
| devices.audio_device.has_value()) { |
| device = &devices.audio_device.value(); |
| } else if (blink::IsVideoInputMediaType(type) && |
| devices.video_device.has_value()) { |
| device = &devices.video_device.value(); |
| } |
| |
| if (!device) { |
| return; |
| } |
| |
| if (!device->display_media_info) { |
| DVLOG(1) << "Tab capture without a DisplayMediaInformation (" << label |
| << ", " << type << ")."; |
| return; |
| } |
| |
| device->display_media_info->capture_handle = capture_handle.Clone(); |
| |
| if (device_capture_handle_change_callback_) { |
| device_capture_handle_change_callback_.Run(label, *device); |
| } |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| void OnZoomLevelChange(const std::string& label, int zoom_level) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!zoom_level_change_callback_) { |
| return; |
| } |
| |
| if (stream_devices_set.stream_devices.size() != 1u) { |
| return; |
| } |
| |
| const blink::mojom::StreamDevices& devices = |
| *stream_devices_set.stream_devices[0]; |
| |
| if (!devices.video_device.has_value()) { |
| return; |
| } |
| |
| const MediaStreamDevice* device = &devices.video_device.value(); |
| if (!device) { |
| return; |
| } |
| |
| if (!device->display_media_info) { |
| DVLOG(1) << "Tab capture without a DisplayMediaInformation (" << label |
| << ")."; |
| return; |
| } |
| |
| zoom_level_change_callback_.Run(label, *device, zoom_level); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| private: |
| DeviceChangedCallback device_changed_callback_; |
| DeviceRequestStateChangeCallback device_request_state_change_callback_; |
| DeviceCaptureConfigurationChangeCallback |
| device_capture_configuration_change_callback_; |
| DeviceCaptureHandleChangeCallback device_capture_handle_change_callback_; |
| ZoomLevelChangeCallback zoom_level_change_callback_; |
| }; |
| |
| class MediaStreamManager::GenerateStreamsRequest |
| : public MediaStreamManager::CreateDeviceRequest { |
| public: |
| GenerateStreamsRequest( |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| bool user_gesture, |
| StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| const StreamControls& controls, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| DeviceStoppedCallback device_stopped_callback, |
| DeviceChangedCallback device_changed_callback, |
| DeviceRequestStateChangeCallback device_request_state_change_callback, |
| DeviceCaptureConfigurationChangeCallback |
| device_capture_configuration_change_callback, |
| DeviceCaptureHandleChangeCallback device_capture_handle_change_callback, |
| ZoomLevelChangeCallback zoom_level_change_callback, |
| GenerateStreamsCallback generate_streams_callback) |
| : CreateDeviceRequest( |
| requesting_render_frame_host_id, |
| requester_id, |
| page_request_id, |
| user_gesture, |
| std::move(audio_stream_selection_info_ptr), |
| blink::MEDIA_GENERATE_STREAM, |
| controls, |
| std::move(salt_and_origin), |
| std::move(device_stopped_callback), |
| std::move(device_changed_callback), |
| std::move(device_request_state_change_callback), |
| std::move(device_capture_configuration_change_callback), |
| std::move(device_capture_handle_change_callback), |
| std::move(zoom_level_change_callback)), |
| generate_streams_callback_(std::move(generate_streams_callback)) { |
| DCHECK(generate_streams_callback_); |
| } |
| |
| ~GenerateStreamsRequest() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (generate_streams_callback_) { |
| std::move(generate_streams_callback_) |
| .Run(MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN, |
| /*label=*/std::string(), |
| /*stream_devices_set=*/nullptr, |
| /*pan_tilt_zoom_allowed=*/false); |
| } |
| } |
| |
| void PanTiltZoomPermissionChecked(const std::string& label, |
| bool pan_tilt_zoom_allowed) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(generate_streams_callback_); |
| |
| MaybeConferTransientActivation(); |
| |
| std::move(generate_streams_callback_) |
| .Run(MediaStreamRequestResult::OK, label, stream_devices_set.Clone(), |
| pan_tilt_zoom_allowed); |
| } |
| |
| void FinalizeRequestFailed(MediaStreamRequestResult result) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(generate_streams_callback_); |
| std::move(generate_streams_callback_) |
| .Run(result, /*label=*/std::string(), |
| /*stream_devices_set=*/nullptr, |
| /*pan_tilt_zoom_allowed=*/false); |
| } |
| |
| private: |
| void MaybeConferTransientActivation() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!base::FeatureList::IsEnabled( |
| media::kGetDisplayMediaConfersActivation)) { |
| return; |
| } |
| |
| // We consciously avoid `IsVideoDesktopCaptureMediaType(video_type())`, |
| // choosing instead to confer transient activation only if screen-sharing is |
| // initiated through getDisplayMedia(). Extending to other screen-sharing |
| // APIs is possible as a series of follow-ups. |
| if (video_type() != MediaStreamType::DISPLAY_VIDEO_CAPTURE && |
| video_type() != MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB) { |
| return; |
| } |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](GlobalRenderFrameHostId rfh_id) { |
| RenderFrameHost* const rfh = RenderFrameHost::FromID(rfh_id); |
| if (!rfh) { |
| return; |
| } |
| rfh->NotifyUserActivation( |
| blink::mojom::UserActivationNotificationType::kInteraction); |
| }, |
| requesting_render_frame_host_id)); |
| } |
| |
| base::WeakPtr<DeviceRequest> GetWeakPtr() override { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| GenerateStreamsCallback generate_streams_callback_; |
| base::WeakPtrFactory<DeviceRequest> weak_factory_{this}; |
| }; |
| |
| class MediaStreamManager::GetOpenDeviceRequest |
| : public MediaStreamManager::CreateDeviceRequest { |
| public: |
| GetOpenDeviceRequest( |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| DeviceStoppedCallback device_stopped_callback, |
| DeviceChangedCallback device_changed_callback, |
| DeviceRequestStateChangeCallback device_request_state_change_callback, |
| DeviceCaptureConfigurationChangeCallback |
| device_capture_configuration_change_callback, |
| DeviceCaptureHandleChangeCallback device_capture_handle_change_callback, |
| ZoomLevelChangeCallback zoom_level_change_callback, |
| GetOpenDeviceCallback get_open_device_callback) |
| : CreateDeviceRequest( |
| requesting_render_frame_host_id, |
| requester_id, |
| page_request_id, |
| /*user_gesture=*/false, |
| /*audio_stream_selection_info_ptr=*/nullptr, |
| blink::MEDIA_GET_OPEN_DEVICE, |
| StreamControls(), |
| std::move(salt_and_origin), |
| std::move(device_stopped_callback), |
| std::move(device_changed_callback), |
| std::move(device_request_state_change_callback), |
| std::move(device_capture_configuration_change_callback), |
| std::move(device_capture_handle_change_callback), |
| std::move(zoom_level_change_callback)), |
| get_open_device_callback_(std::move(get_open_device_callback)) { |
| DCHECK(get_open_device_callback_); |
| } |
| |
| ~GetOpenDeviceRequest() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (get_open_device_callback_) { |
| std::move(get_open_device_callback_) |
| .Run(MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN, nullptr); |
| } |
| } |
| |
| void PanTiltZoomPermissionChecked(const std::string& label, |
| bool pan_tilt_zoom_allowed) override { |
| DCHECK(get_open_device_callback_); |
| // GetOpenDevice is only available with exactly one stream. |
| DCHECK_EQ(stream_devices_set.stream_devices.size(), 1u); |
| const blink::mojom::StreamDevices& stream_devices = |
| *stream_devices_set.stream_devices[0]; |
| // GetOpenDevice should return exactly one device, which can be of either |
| // audio or video type. |
| DCHECK_NE(stream_devices.audio_device.has_value(), |
| stream_devices.video_device.has_value()); |
| MediaStreamDevice device = blink::IsVideoInputMediaType(video_type()) |
| ? stream_devices.video_device.value() |
| : stream_devices.audio_device.value(); |
| |
| std::move(get_open_device_callback_) |
| .Run(MediaStreamRequestResult::OK, |
| GetOpenDeviceResponse::New(label, device, pan_tilt_zoom_allowed)); |
| } |
| |
| void FinalizeRequestFailed(MediaStreamRequestResult result) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(get_open_device_callback_); |
| std::move(get_open_device_callback_).Run(result, /*response=*/nullptr); |
| } |
| |
| private: |
| base::WeakPtr<DeviceRequest> GetWeakPtr() override { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| // This callback is used by transferred MediaStreamTracks to access and clone |
| // an existing open MediaStreamDevice (identified by its session_id). If the |
| // device is found, it is returned to this callback along with a |
| // MediaStreamRequestResult::OK. Otherwise, returns |
| // MediaStreamRequestResult::INVALID_STATE along with std::nullopt instead of |
| // a MediaStreamDevice. |
| GetOpenDeviceCallback get_open_device_callback_; |
| base::WeakPtrFactory<DeviceRequest> weak_factory_{this}; |
| }; |
| |
| class MediaStreamManager::OpenDeviceRequest |
| : public MediaStreamManager::DeviceRequest { |
| public: |
| OpenDeviceRequest(GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| const StreamControls& controls, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| DeviceStoppedCallback device_stopped_callback, |
| OpenDeviceCallback open_device_callback) |
| : DeviceRequest(requesting_render_frame_host_id, |
| requester_id, |
| page_request_id, |
| /*user gesture=*/false, |
| // For pepper, we default to searching for a device always |
| // based on device ID, independently of whether the |
| // request is for an audio or a video device. |
| StreamSelectionInfo::NewSearchOnlyByDeviceId({}), |
| blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY, |
| controls, |
| std::move(salt_and_origin), |
| std::move(device_stopped_callback)), |
| open_device_callback_(std::move(open_device_callback)) { |
| DCHECK(open_device_callback_); |
| } |
| |
| ~OpenDeviceRequest() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (open_device_callback_) { |
| std::move(open_device_callback_) |
| .Run(/*success=*/false, std::string(), MediaStreamDevice()); |
| } |
| } |
| |
| void FinalizeRequest(const std::string& label) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(open_device_callback_); |
| SendLogMessage(base::StringPrintf( |
| "FinalizeOpenDevice({label=%s}, {requester_id=" |
| "%d}, {request_type=%s})", |
| label.c_str(), requester_id, RequestTypeToString(request_type()))); |
| std::move(open_device_callback_) |
| .Run(/*success=*/true, label, |
| blink::ToMediaStreamDevicesList(stream_devices_set).front()); |
| } |
| |
| void FinalizeRequestFailed(MediaStreamRequestResult result) override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(open_device_callback_); |
| std::move(open_device_callback_) |
| .Run(/*success=*/false, /*label=*/std::string(), MediaStreamDevice()); |
| } |
| |
| private: |
| base::WeakPtr<DeviceRequest> GetWeakPtr() override { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| // This callback is only used by pepper and tries to open the device |
| // identified by device_id. If it is opened successfully, it returns this |
| // device. Otherwise, returns an empty device. |
| OpenDeviceCallback open_device_callback_; |
| base::WeakPtrFactory<DeviceRequest> weak_factory_{this}; |
| }; |
| |
| // static |
| void MediaStreamManager::SendMessageToNativeLog(const std::string& message) { |
| if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::SendMessageToNativeLog, message)); |
| return; |
| } |
| VLOG(1) << message; |
| |
| if (!media_stream_manager) { |
| // MediaStreamManager hasn't been initialized. This is allowed in tests. |
| return; |
| } |
| |
| media_stream_manager->AddLogMessageOnIOThread(message); |
| } |
| |
| // static |
| MediaStreamManager* MediaStreamManager::GetInstance() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return media_stream_manager; |
| } |
| |
| MediaStreamManager::MediaStreamManager(media::AudioSystem* audio_system) |
| : MediaStreamManager(audio_system, nullptr) { |
| SendLogMessage(base::StringPrintf("MediaStreamManager([this=%p]))", this)); |
| } |
| |
| MediaStreamManager::MediaStreamManager( |
| media::AudioSystem* audio_system, |
| std::unique_ptr<VideoCaptureProvider> video_capture_provider) |
| : |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| captured_surface_controller_factory_( |
| MakeDefaultCapturedSurfaceControllerFactory()), |
| #endif |
| audio_system_(audio_system) { |
| bool use_fake_ui_factory = false; |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kUseFakeUIForMediaStream) && |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| switches::kUseFakeDeviceForMediaStream) != "deny") { |
| use_fake_ui_factory = true; |
| } |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kAutoAcceptCameraAndMicrophoneCapture)) { |
| // Crashing the browser process is actually the user-friendly thing to do, |
| // as it informs the user of their mistake early. |
| CHECK(!use_fake_ui_factory) << "Mutually exclusive command line flags."; |
| use_fake_ui_factory = true; |
| use_fake_ui_only_for_camera_and_microphone_ = true; |
| } |
| |
| if (use_fake_ui_factory) { |
| fake_ui_factory_ = base::BindRepeating([] { |
| return std::make_unique<FakeMediaStreamUIProxy>( |
| /*tests_use_fake_render_frame_hosts=*/false); |
| }); |
| } |
| |
| if (base::FeatureList::IsEnabled(media::kUseFakeDeviceForMediaStream)) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kUseFakeDeviceForMediaStream); |
| } |
| |
| DCHECK(audio_system_); |
| |
| if (!video_capture_provider) { |
| scoped_refptr<base::SingleThreadTaskRunner> device_task_runner; |
| |
| #if BUILDFLAG(IS_MAC) |
| // On MacOS the main thread must be used to run VideoCaptureDevice. |
| device_task_runner = base::SingleThreadTaskRunner::GetCurrentDefault(); |
| #else // !BUILDFLAG(IS_MAC) |
| // For all platforms other than MacOS start a new thread. |
| video_capture_thread_.emplace("VideoCaptureThread"); |
| base::Thread::Options thread_options; |
| #if BUILDFLAG(IS_WIN) |
| // Use an STA Video Capture Thread to try to avoid crashes on enumeration |
| // of buggy third party Direct Show modules, http://crbug.com/428958. |
| video_capture_thread_->init_com_with_mta(false); |
| thread_options.message_pump_type = base::MessagePumpType::UI; |
| #elif BUILDFLAG(IS_FUCHSIA) |
| // On Fuchsia IO thread is required for FIDL connections. |
| thread_options.message_pump_type = base::MessagePumpType::IO; |
| #endif |
| CHECK(video_capture_thread_->StartWithOptions(std::move(thread_options))); |
| device_task_runner = video_capture_thread_->task_runner(); |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (media::ShouldUseCrosCameraService()) { |
| jpeg_accelerator_provider_ = |
| std::make_unique<media::JpegAcceleratorProviderImpl>( |
| base::BindRepeating( |
| &VideoCaptureDependencies::CreateJpegDecodeAccelerator), |
| base::BindRepeating( |
| &VideoCaptureDependencies::CreateJpegEncodeAccelerator)); |
| system_event_monitor_ = std::make_unique<media::SystemEventMonitorImpl>(); |
| media::CameraHalDispatcherImpl::GetInstance()->Start(); |
| } |
| #endif |
| video_capture_provider = std::make_unique<VideoCaptureProviderSwitcher>( |
| std::make_unique<ServiceVideoCaptureProvider>( |
| base::BindRepeating(&SendVideoCaptureLogMessage)), |
| InProcessVideoCaptureProvider::CreateInstanceForScreenCapture( |
| std::move(device_task_runner))); |
| } |
| InitializeMaybeAsync(std::move(video_capture_provider)); |
| |
| audio_service_listener_ = std::make_unique<AudioServiceListener>(); |
| |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |
| if (base::FeatureList::IsEnabled(kReleaseVideoSourceProviderIfNotInUse)) { |
| video_capture_hosts_.set_disconnect_handler(base::BindRepeating( |
| &MediaStreamManager::OnVideoCaptureHostConnectionError, |
| base::Unretained(this))); |
| } |
| #endif |
| } |
| |
| MediaStreamManager::~MediaStreamManager() { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO)); |
| DCHECK(requests_.empty()); |
| } |
| |
| VideoCaptureManager* MediaStreamManager::video_capture_manager() const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(video_capture_manager_.get()); |
| return video_capture_manager_.get(); |
| } |
| |
| AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager() |
| const { |
| // May be called on any thread, provided that we are not in shutdown. |
| DCHECK(audio_input_device_manager_.get()); |
| return audio_input_device_manager_.get(); |
| } |
| |
| AudioServiceListener* MediaStreamManager::audio_service_listener() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return audio_service_listener_.get(); |
| } |
| |
| MediaDevicesManager* MediaStreamManager::media_devices_manager() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // nullptr might be returned during shutdown. |
| return media_devices_manager_.get(); |
| } |
| |
| media::AudioSystem* MediaStreamManager::audio_system() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| return audio_system_; |
| } |
| |
| PreferredAudioOutputDeviceManager* |
| MediaStreamManager::preferred_audio_output_device_manager() { |
| return preferred_audio_output_device_manager_.get(); |
| } |
| |
| void MediaStreamManager::AddVideoCaptureObserver( |
| media::VideoCaptureObserver* capture_observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (video_capture_manager_) { |
| video_capture_manager_->AddVideoCaptureObserver(capture_observer); |
| } |
| } |
| |
| void MediaStreamManager::RemoveAllVideoCaptureObservers() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (video_capture_manager_) { |
| video_capture_manager_->RemoveAllVideoCaptureObservers(); |
| } |
| } |
| |
| std::string MediaStreamManager::MakeMediaAccessRequest( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| const StreamControls& controls, |
| const url::Origin& security_origin, |
| MediaAccessRequestCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| auto request = std::make_unique<MediaAccessRequest>( |
| render_frame_host_id, requester_id, page_request_id, controls, |
| MediaDeviceSaltAndOrigin( |
| /*device_id_salt=*/std::string(), /*origin=*/security_origin, |
| /*group_id_salt=*/std::string(), |
| /*has_focus=*/true, /*is_background=*/false), |
| std::move(callback)); |
| const std::string& label = AddRequest(std::move(request))->first; |
| |
| // Post a task and handle the request asynchronously. The reason is that the |
| // requester won't have a label for the request until this function returns |
| // and thus can not handle a response. Using base::Unretained is safe since |
| // MediaStreamManager is deleted on the UI thread, after the IO thread has |
| // been stopped. |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&MediaStreamManager::SetUpRequest, |
| base::Unretained(this), label)); |
| return label; |
| } |
| |
| void MediaStreamManager::GenerateStreams( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| const StreamControls& controls, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| bool user_gesture, |
| StreamSelectionInfoPtr audio_stream_selection_info_ptr, |
| GenerateStreamsCallback generate_streams_callback, |
| DeviceStoppedCallback device_stopped_callback, |
| DeviceChangedCallback device_changed_callback, |
| DeviceRequestStateChangeCallback device_request_state_change_callback, |
| DeviceCaptureConfigurationChangeCallback |
| device_capture_configuration_change_callback, |
| DeviceCaptureHandleChangeCallback device_capture_handle_change_callback, |
| ZoomLevelChangeCallback zoom_level_change_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(GetGenerateStreamsLogString(render_frame_host_id, requester_id, |
| page_request_id)); |
| std::unique_ptr<DeviceRequest> request = |
| std::make_unique<GenerateStreamsRequest>( |
| render_frame_host_id, requester_id, page_request_id, user_gesture, |
| std::move(audio_stream_selection_info_ptr), controls, |
| std::move(salt_and_origin), std::move(device_stopped_callback), |
| std::move(device_changed_callback), |
| std::move(device_request_state_change_callback), |
| std::move(device_capture_configuration_change_callback), |
| std::move(device_capture_handle_change_callback), |
| std::move(zoom_level_change_callback), |
| std::move(generate_streams_callback)); |
| DeviceRequests::const_iterator request_it = AddRequest(std::move(request)); |
| const std::string& label = request_it->first; |
| |
| if (generate_stream_test_callback_) { |
| // The test callback is responsible to verify whether the |controls| is |
| // as expected. Then we need to finish getUserMedia and let Javascript |
| // access the result. |
| if (std::move(generate_stream_test_callback_).Run(controls)) { |
| FinalizeGenerateStreams(label, request_it->second.get()); |
| } else { |
| // This will invalidate both |label| and |request_it|. |
| FinalizeRequestFailed(request_it, |
| MediaStreamRequestResult::INVALID_STATE); |
| } |
| return; |
| } |
| |
| // Post a task and handle the request asynchronously. The reason is that the |
| // requester won't have a label for the request until this function returns |
| // and thus can not handle a response. Using base::Unretained is safe since |
| // MediaStreamManager is deleted on the UI thread, after the IO thread has |
| // been stopped. |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&MediaStreamManager::SetUpRequest, |
| base::Unretained(this), label)); |
| } |
| |
| void MediaStreamManager::GetOpenDevice( |
| const base::UnguessableToken& device_session_id, |
| const base::UnguessableToken& transfer_id, |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| GetOpenDeviceCallback get_open_device_callback, |
| DeviceStoppedCallback device_stopped_callback, |
| DeviceChangedCallback device_changed_callback, |
| DeviceRequestStateChangeCallback device_request_state_change_callback, |
| DeviceCaptureConfigurationChangeCallback |
| device_capture_configuration_change_callback, |
| DeviceCaptureHandleChangeCallback device_capture_handle_change_callback, |
| ZoomLevelChangeCallback zoom_level_change_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer)); |
| |
| std::unique_ptr<DeviceRequest> request = |
| std::make_unique<GetOpenDeviceRequest>( |
| render_frame_host_id, requester_id, page_request_id, |
| std::move(salt_and_origin), std::move(device_stopped_callback), |
| std::move(device_changed_callback), |
| std::move(device_request_state_change_callback), |
| std::move(device_capture_configuration_change_callback), |
| std::move(device_capture_handle_change_callback), |
| std::move(zoom_level_change_callback), |
| std::move(get_open_device_callback)); |
| |
| DeviceRequests::const_iterator request_it = AddRequest(std::move(request)); |
| const std::string& new_label = request_it->first; |
| DeviceRequest* const request_ptr = request_it->second.get(); |
| |
| const std::optional<MediaStreamDevice> new_device = |
| CloneExistingOpenDevice(device_session_id, transfer_id, new_label); |
| if (!new_device.has_value()) { |
| // No existing device with matching session id is found. |
| // This invalidates |request_it|. |
| FinalizeRequestFailed(request_it, MediaStreamRequestResult::INVALID_STATE); |
| return; |
| } |
| |
| request_ptr->stream_devices_set.stream_devices.emplace_back( |
| blink::mojom::StreamDevices::New()); |
| if (blink::IsAudioInputMediaType(new_device->type)) { |
| request_ptr->stream_devices_set.stream_devices[0]->audio_device = |
| *new_device; |
| request_ptr->SetAudioType(new_device->type); |
| } else if (blink::IsVideoInputMediaType(new_device->type)) { |
| request_ptr->stream_devices_set.stream_devices[0]->video_device = |
| *new_device; |
| request_ptr->SetVideoType(new_device->type); |
| } |
| |
| // Device cloned in CloneExistingOpenDevice is ensured to have the state |
| // MEDIA_REQUEST_STATE_DONE. |
| // Set state to MEDIA_REQUEST_STATE_DONE as all processing specific to |
| // new_device has been done. |
| request_ptr->SetState(new_device->type, MEDIA_REQUEST_STATE_DONE); |
| HandleRequestDone(new_label, request_ptr); |
| } |
| |
| void MediaStreamManager::CancelRequest( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (auto request_it = requests_.begin(); request_it != requests_.end(); |
| ++request_it) { |
| const DeviceRequest* const request = request_it->second.get(); |
| if (request->requesting_render_frame_host_id == render_frame_host_id && |
| request->requester_id == requester_id && |
| request->page_request_id == page_request_id) { |
| CancelRequest(request_it); |
| return; |
| } |
| } |
| } |
| |
| void MediaStreamManager::CancelRequest(const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const DeviceRequests::const_iterator request_it = FindRequestIterator(label); |
| if (request_it == requests_.end()) { |
| SendLogMessage( |
| base::StringPrintf("CancelRequest({label=%s})", label.c_str())); |
| LOG(ERROR) << "The request with label = " << label << " does not exist."; |
| return; |
| } |
| |
| CancelRequest(request_it); |
| } |
| |
| void MediaStreamManager::CancelAllRequests( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequests::const_iterator request_it = requests_.begin(); |
| while (request_it != requests_.end()) { |
| if (request_it->second->requesting_render_frame_host_id != |
| render_frame_host_id || |
| request_it->second->requester_id != requester_id) { |
| ++request_it; |
| continue; |
| } |
| |
| const DeviceRequests::const_iterator next = std::next(request_it); |
| CancelRequest(request_it); |
| request_it = next; |
| } |
| } |
| |
| void MediaStreamManager::StopStreamDevice( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| const std::string& device_id, |
| const base::UnguessableToken& session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(GetStopStreamDeviceLogString( |
| render_frame_host_id, requester_id, device_id, session_id)); |
| |
| // Find the first request for this `render_frame_host_id` |
| // of type MEDIA_GENERATE_STREAM, MEDIA_DEVICE_UPDATE or MEDIA_GET_OPEN_DEVICE |
| // that had requested to use device with Id |device_id| and sessionId |
| // |session_id| and is now requesting to stop it. |
| for (const LabeledDeviceRequest& device_request : requests_) { |
| DeviceRequest* const request = device_request.second.get(); |
| if (request->requesting_render_frame_host_id != render_frame_host_id || |
| request->requester_id != requester_id) { |
| continue; |
| } |
| switch (request->request_type()) { |
| case blink::MEDIA_DEVICE_ACCESS: |
| case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: |
| break; |
| case blink::MEDIA_DEVICE_UPDATE: |
| case blink::MEDIA_GENERATE_STREAM: |
| case blink::MEDIA_GET_OPEN_DEVICE: |
| for (const auto& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::MediaStreamDevice* const device = |
| GetStreamDevice(*stream_devices_ptr, session_id); |
| if (!device || device->id != device_id) { |
| continue; |
| } |
| |
| if (request->IsTransferMapEmpty(device->type)) { |
| // There are no ongoing transfers for this device. |
| StopDevice(device->type, device->session_id()); |
| } else { |
| request->SetShouldStopInFuture(device->type, |
| /*should_be_stopped=*/true); |
| } |
| return; |
| } |
| break; |
| } |
| } |
| } |
| |
| bool MediaStreamManager::KeepDeviceAliveForTransfer( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| const base::UnguessableToken& session_id, |
| const base::UnguessableToken& transfer_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer)); |
| |
| for (const LabeledDeviceRequest& device_request : requests_) { |
| DeviceRequest* const request = device_request.second.get(); |
| switch (request->request_type()) { |
| case blink::MEDIA_DEVICE_ACCESS: |
| case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: |
| break; |
| case blink::MEDIA_DEVICE_UPDATE: |
| case blink::MEDIA_GENERATE_STREAM: |
| case blink::MEDIA_GET_OPEN_DEVICE: |
| for (const auto& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::MediaStreamDevice* const device = |
| GetStreamDevice(*stream_devices_ptr, session_id); |
| if (!device) { |
| continue; |
| } |
| |
| UpdateDeviceTransferStatus(request, device, transfer_id, |
| TransferState::KEPT_ALIVE); |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| base::UnguessableToken MediaStreamManager::VideoDeviceIdToSessionId( |
| const std::string& device_id) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (const LabeledDeviceRequest& device_request : requests_) { |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| device_request.second->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& devices = *stream_devices_ptr; |
| if (devices.video_device.has_value() && |
| devices.video_device->id == device_id && |
| devices.video_device->type == MediaStreamType::DEVICE_VIDEO_CAPTURE) { |
| return devices.video_device->session_id(); |
| } |
| } |
| } |
| return base::UnguessableToken(); |
| } |
| |
| void MediaStreamManager::StopDevice(MediaStreamType type, |
| const base::UnguessableToken& session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf("StopDevice({type=%s}, {session_id=%s})", |
| StreamTypeToString(type), |
| session_id.ToString().c_str())); |
| DeviceRequests::const_iterator request_it = requests_.begin(); |
| while (request_it != requests_.end()) { |
| DeviceRequest* request = request_it->second.get(); |
| |
| if (request->stream_devices_set.stream_devices.empty()) { |
| // There is no device in use yet by this request. |
| ++request_it; |
| continue; |
| } |
| |
| auto stream_devices_set_iterator = |
| request->stream_devices_set.stream_devices.begin(); |
| while (stream_devices_set_iterator != |
| request->stream_devices_set.stream_devices.end()) { |
| blink::mojom::StreamDevicesPtr& stream_devices_ptr = |
| *stream_devices_set_iterator; |
| blink::mojom::StreamDevices& devices = *stream_devices_ptr; |
| if (devices.audio_device.has_value() && |
| devices.audio_device->type == type && |
| devices.audio_device->session_id() == session_id) { |
| if (request->state(type) == MEDIA_REQUEST_STATE_DONE) { |
| CloseDevice(type, session_id); |
| } |
| devices.audio_device = std::nullopt; |
| } |
| if (devices.video_device.has_value() && |
| devices.video_device->type == type && |
| devices.video_device->session_id() == session_id) { |
| if (request->state(type) == MEDIA_REQUEST_STATE_DONE) { |
| CloseDevice(type, session_id); |
| } |
| devices.video_device = std::nullopt; |
| } |
| |
| if (!devices.audio_device.has_value() && |
| !devices.video_device.has_value()) { |
| stream_devices_set_iterator = |
| request->stream_devices_set.stream_devices.erase( |
| stream_devices_set_iterator); |
| } else { |
| ++stream_devices_set_iterator; |
| } |
| } |
| |
| // If this request doesn't have any active devices after a device |
| // has been stopped above, remove the request. Note that the request is |
| // only deleted if a device has been removed from |devices|. |
| if (request->stream_devices_set.stream_devices.empty()) { |
| const DeviceRequests::const_iterator next = std::next(request_it); |
| DeleteRequest(request_it); |
| request_it = next; |
| } else { |
| ++request_it; |
| } |
| } |
| } |
| |
| void MediaStreamManager::CloseDevice(MediaStreamType type, |
| const base::UnguessableToken& session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf("CloseDevice({type=%s}, {session_id=%s})", |
| StreamTypeToString(type), |
| session_id.ToString().c_str())); |
| GetDeviceManager(type)->Close(session_id); |
| |
| for (const LabeledDeviceRequest& labeled_request : requests_) { |
| DeviceRequest* const request = labeled_request.second.get(); |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (device.session_id() != session_id || device.type != type) { |
| continue; |
| } |
| |
| MaybeStopTrackingCaptureHandleConfig(labeled_request.first, device); |
| // Notify observers that this device is being closed. |
| // Note that only one device per type can be opened. |
| request->SetState(type, MEDIA_REQUEST_STATE_CLOSING); |
| // AudioInputDeviceManager does not have a mechanism to stop the audio |
| // stream when the session is closed, while VideoCaptureManager does. |
| // To ensure consistent behavior when sessions are closed, use the |
| // stop callback to stop audio streams. |
| if (blink::IsAudioInputMediaType(device.type) && |
| request->device_stopped_callback) { |
| request->device_stopped_callback.Run(labeled_request.first, device); |
| } |
| if (request->ui_proxy) { |
| const DesktopMediaID media_id = DesktopMediaID::Parse(device.id); |
| if (!media_id.is_null()) { |
| request->ui_proxy->OnDeviceStopped(labeled_request.first, media_id); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void MediaStreamManager::OpenDevice( |
| GlobalRenderFrameHostId render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| const std::string& device_id, |
| MediaStreamType type, |
| MediaDeviceSaltAndOrigin salt_and_origin, |
| OpenDeviceCallback open_device_callback, |
| DeviceStoppedCallback device_stopped_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(type == MediaStreamType::DEVICE_AUDIO_CAPTURE || |
| type == MediaStreamType::DEVICE_VIDEO_CAPTURE); |
| SendLogMessage(GetOpenDeviceLogString(render_frame_host_id, requester_id, |
| page_request_id, device_id, type)); |
| StreamControls controls; |
| if (blink::IsAudioInputMediaType(type)) { |
| controls.audio.stream_type = type; |
| controls.audio.device_ids.push_back(device_id); |
| } else if (blink::IsVideoInputMediaType(type)) { |
| controls.video.stream_type = type; |
| controls.video.device_ids.push_back(device_id); |
| } else { |
| NOTREACHED(); |
| } |
| auto request = std::make_unique<OpenDeviceRequest>( |
| render_frame_host_id, requester_id, page_request_id, controls, |
| std::move(salt_and_origin), std::move(device_stopped_callback), |
| std::move(open_device_callback)); |
| const std::string& label = AddRequest(std::move(request))->first; |
| |
| // Post a task and handle the request asynchronously. The reason is that the |
| // requester won't have a label for the request until this function returns |
| // and thus can not handle a response. Using base::Unretained is safe since |
| // MediaStreamManager is deleted on the UI thread, after the IO thread has |
| // been stopped. |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&MediaStreamManager::SetUpRequest, |
| base::Unretained(this), label)); |
| } |
| |
| void MediaStreamManager::EnsureDeviceMonitorStarted() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Call `EnumerateDevices` to start monitoring and ensure that the observers |
| // are notified at least once. |
| MediaDevicesManager::BoolDeviceTypes types; |
| types.fill(true); |
| media_devices_manager_->EnumerateDevices(types, base::DoNothing()); |
| } |
| |
| void MediaStreamManager::StopRemovedDevice( |
| MediaDeviceType type, |
| const blink::WebMediaDeviceInfo& media_device_info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(type == MediaDeviceType::kMediaAudioInput || |
| type == MediaDeviceType::kMediaVideoInput); |
| SendLogMessage(base::StringPrintf( |
| "StopRemovedDevice({type=%s}, {device=[id: %s, name: %s]}", |
| DeviceTypeToString(type), |
| media_device_info.device_id.c_str(), |
| media_device_info.label.c_str()) |
| .c_str()); |
| |
| MediaStreamType stream_type = ConvertToMediaStreamType(type); |
| std::vector<base::UnguessableToken> session_ids; |
| for (const LabeledDeviceRequest& labeled_request : requests_) { |
| const DeviceRequest* request = labeled_request.second.get(); |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| const std::string source_id = GetHMACForRawMediaDeviceID( |
| request->salt_and_origin, media_device_info.device_id); |
| if (device.id == source_id && device.type == stream_type) { |
| session_ids.push_back(device.session_id()); |
| if (request->device_stopped_callback) { |
| request->device_stopped_callback.Run(labeled_request.first, device); |
| } |
| } |
| } |
| } |
| } |
| for (const auto& session_id : session_ids) { |
| StopDevice(stream_type, session_id); |
| } |
| } |
| |
| bool MediaStreamManager::RemoveInvalidDeviceIds( |
| const MediaDeviceSaltAndOrigin& salt_and_origin, |
| const TrackControls& controls, |
| const blink::WebMediaDeviceInfoArray& devices, |
| std::vector<std::string>* device_ids) const { |
| if (controls.device_ids.empty()) { |
| return true; |
| } |
| for (const auto& controls_device_id : controls.device_ids) { |
| std::string device_id; |
| if (!GetDeviceIDAndGroupIDFromHMAC(salt_and_origin, controls_device_id, |
| devices, &device_id, |
| /*group_id=*/nullptr)) { |
| LOG(WARNING) << "Invalid device ID = " << controls_device_id; |
| continue; |
| } |
| device_ids->push_back(device_id); |
| } |
| if (device_ids->empty()) { |
| LOG(WARNING) << "No valid device IDs"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool MediaStreamManager::GetEligibleCaptureDeviceids( |
| const DeviceRequest* request, |
| MediaStreamType type, |
| const blink::WebMediaDeviceInfoArray& devices, |
| std::vector<std::string>* device_ids) const { |
| if (type == MediaStreamType::DEVICE_AUDIO_CAPTURE) { |
| return RemoveInvalidDeviceIds(request->salt_and_origin, |
| request->stream_controls().audio, devices, |
| device_ids); |
| } else if (type == MediaStreamType::DEVICE_VIDEO_CAPTURE) { |
| return RemoveInvalidDeviceIds(request->salt_and_origin, |
| request->stream_controls().video, devices, |
| device_ids); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void MediaStreamManager::TranslateDeviceIdToSourceId( |
| const DeviceRequest* request, |
| MediaStreamDevice* device) const { |
| // If the device id contains sensitive information, we replace it with a hash |
| // so that it is not exposed to the Renderer. This happens in the following |
| // cases: |
| // * If the MediaStreamDevice represents a video or audio OS input device |
| // (such as a webcam or microphone), the device id may contain an identifier |
| // supplied by the OS. |
| // * If it is an application loopback device, it may contain an identifier |
| // (such as a process id) supplied by the OS. |
| if (blink::IsDeviceMediaType(request->audio_type()) || |
| blink::IsDeviceMediaType(request->video_type()) || |
| IsApplicationLoopbackAudioDevice(device)) { |
| device->id = |
| GetHMACForRawMediaDeviceID(request->salt_and_origin, device->id); |
| if (device->group_id) { |
| device->group_id = GetHMACForRawMediaDeviceID( |
| request->salt_and_origin, *device->group_id, /*use_group_salt=*/true); |
| } |
| } |
| } |
| |
| void MediaStreamManager::StartEnumeration(DeviceRequest* request, |
| const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage( |
| base::StringPrintf("StartEnumeration({requester_id=%d}, {label=%s})", |
| request->requester_id, label.c_str())); |
| |
| bool request_audio_input = |
| request->audio_type() != MediaStreamType::NO_SERVICE; |
| bool request_video_input = |
| request->video_type() != MediaStreamType::NO_SERVICE; |
| |
| MediaDevicesManager::DeviceStartMonitoringMode start_mode = |
| MediaDevicesManager::DeviceStartMonitoringMode::kNone; |
| if (request_audio_input && request_video_input) { |
| start_mode = |
| MediaDevicesManager::DeviceStartMonitoringMode::kStartAudioAndVideo; |
| } else if (request_audio_input) { |
| start_mode = MediaDevicesManager::DeviceStartMonitoringMode::kStartAudio; |
| } else if (request_video_input) { |
| start_mode = MediaDevicesManager::DeviceStartMonitoringMode::kStartVideo; |
| } |
| // Start monitoring the requested devices when doing the first enumeration. |
| media_devices_manager_->StartMonitoring(start_mode); |
| |
| // Start enumeration for devices of all requested device types. |
| if (request_audio_input) { |
| request->SetState(request->audio_type(), MEDIA_REQUEST_STATE_REQUESTED); |
| } |
| |
| if (request_video_input) { |
| request->SetState(request->video_type(), MEDIA_REQUEST_STATE_REQUESTED); |
| } |
| |
| // base::Unretained is safe here because MediaStreamManager is deleted on the |
| // UI thread, after the IO thread has been stopped. |
| DCHECK(request_audio_input || request_video_input); |
| MediaDevicesManager::BoolDeviceTypes devices_to_enumerate; |
| devices_to_enumerate[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)] = |
| request_audio_input; |
| devices_to_enumerate[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)] = |
| request_video_input; |
| media_devices_manager_->EnumerateDevices( |
| devices_to_enumerate, |
| base::BindOnce(&MediaStreamManager::DevicesEnumerated, |
| base::Unretained(this), request_audio_input, |
| request_video_input, label)); |
| } |
| |
| MediaStreamManager::DeviceRequests::const_iterator |
| MediaStreamManager::AddRequest(std::unique_ptr<DeviceRequest> request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Create a label for this request and verify it is unique. |
| std::string unique_label; |
| do { |
| unique_label = base::Uuid::GenerateRandomV4().AsLowercaseString(); |
| } while (FindRequest(unique_label) != nullptr); |
| |
| SendLogMessage( |
| base::StringPrintf("AddRequest([requester_id=%d]) => (label=%s)", |
| request->requester_id, unique_label.c_str())); |
| request->SetLabel(unique_label); |
| requests_.push_back(std::make_pair(unique_label, std::move(request))); |
| |
| return std::prev(requests_.end()); |
| } |
| |
| MediaStreamManager::DeviceRequests::const_iterator |
| MediaStreamManager::FindRequestIterator(const std::string& label) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| return std::ranges::find(requests_, label, &LabeledDeviceRequest::first); |
| } |
| |
| MediaStreamManager::DeviceRequest* MediaStreamManager::FindRequest( |
| const std::string& label) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| MediaStreamManager::DeviceRequests::const_iterator it = |
| FindRequestIterator(label); |
| return (it != requests_.end()) ? it->second.get() : nullptr; |
| } |
| |
| MediaStreamManager::DeviceRequest* |
| MediaStreamManager::FindRequestByVideoSessionId( |
| const base::UnguessableToken& session_id) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (const LabeledDeviceRequest& labeled_request : requests_) { |
| DeviceRequest* const request = labeled_request.second.get(); |
| if (!request) { |
| continue; |
| } |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const std::optional<blink::MediaStreamDevice>& video_device = |
| stream_devices_ptr->video_device; |
| if (video_device && video_device->serializable_session_id().has_value() && |
| video_device->serializable_session_id().value() == session_id) { |
| return request; |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| CapturedSurfaceController* MediaStreamManager::GetCapturedSurfaceController( |
| GlobalRenderFrameHostId capturer_rfh_id, |
| const base::UnguessableToken& session_id, |
| blink::mojom::CapturedSurfaceControlResult& result) { |
| DeviceRequest* const request = FindRequestByVideoSessionId(session_id); |
| if (!request) { |
| result = CapturedSurfaceControlResult::kCapturedSurfaceNotFoundError; |
| return nullptr; |
| } |
| |
| if (request->requesting_render_frame_host_id != capturer_rfh_id) { |
| result = CapturedSurfaceControlResult::kUnknownError; |
| return nullptr; |
| } |
| |
| CapturedSurfaceController* const controller = |
| request->captured_surface_controller(); |
| if (!controller) { |
| result = blink::mojom::CapturedSurfaceControlResult::kUnknownError; |
| return nullptr; |
| } |
| |
| result = blink::mojom::CapturedSurfaceControlResult::kSuccess; |
| return controller; |
| } |
| #endif |
| |
| std::optional<MediaStreamDevice> MediaStreamManager::CloneExistingOpenDevice( |
| const base::UnguessableToken& existing_device_session_id, |
| const base::UnguessableToken& transfer_id, |
| const std::string& new_label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequest* const new_request = FindRequest(new_label); |
| DCHECK(new_request); |
| // TODO(crbug.com/40846554): Generalize to multiple streams. |
| DCHECK(new_request->stream_devices_set.stream_devices.empty()); |
| for (const LabeledDeviceRequest& labeled_request : requests_) { |
| DeviceRequest* const existing_request = labeled_request.second.get(); |
| // Skipping requests that contain multiple streams. |
| // TODO(crbug.com/40846554): Generalize to multiple streams. |
| if (existing_request->stream_devices_set.stream_devices.size() > 1u) { |
| continue; |
| } |
| for (const auto& stream_devices_ptr : |
| existing_request->stream_devices_set.stream_devices) { |
| const blink::MediaStreamDevice* const existing_device = |
| GetStreamDevice(*stream_devices_ptr, existing_device_session_id); |
| |
| if (!existing_device) { |
| continue; |
| } |
| if (existing_request->state(existing_device->type) != |
| MEDIA_REQUEST_STATE_DONE) { |
| // TODO(crbug.com/40058526): Ensure state of MediaStreamDevice |
| // doesn't change while MediaStreamTrack is being transferred. |
| // Skip devices not in state MEDIA_REQUEST_STATE_DONE. |
| continue; |
| } |
| |
| MediaStreamDevice new_device = *existing_device; |
| if (!blink::IsMediaStreamDeviceTransferrable(*existing_device)) { |
| // TODO(crbug.com/40058526): Remove bad message after transfer |
| // is supported for these stream types. |
| // TODO(crbug.com/40058526): Hash device id and group_id for |
| // MediaStreamType DEVICE_AUDIO_CAPTURE and DEVICE_VIDEO_CAPTURE. |
| ReceivedBadMessage( |
| new_request->requesting_render_frame_host_id.child_id, |
| bad_message::MSM_GET_OPEN_DEVICE_FOR_UNSUPPORTED_STREAM_TYPE); |
| return std::nullopt; |
| } |
| |
| new_device.set_session_id( |
| GetDeviceManager(new_device.type)->Open(new_device)); |
| UpdateDeviceTransferStatus(existing_request, existing_device, transfer_id, |
| TransferState::GOT_OPEN_DEVICE); |
| return new_device; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| void MediaStreamManager::UpdateDeviceTransferStatus( |
| DeviceRequest* request, |
| const blink::MediaStreamDevice* const device, |
| const base::UnguessableToken& transfer_id, |
| TransferState transfer_state) { |
| // TODO(crbug.com/40058526): Use |start_time| to enforce a timeout to |
| // stop device in case a transfer never completes. |
| MediaStreamType stream_type = device->type; |
| std::optional<TransferState> existing_state = |
| request->GetTransferState(stream_type, transfer_id); |
| if (!existing_state) { |
| request->SetTransferState(stream_type, transfer_id, transfer_state); |
| return; |
| } |
| |
| if (existing_state.value() != transfer_state) { |
| // If the new |transfer_state| is different from the existing state in |
| // |transfer_map|, this entry can be removed. This is because reaching here |
| // implies both states, KEPT_ALIVE and GOT_OPEN_DEVICE, have been achieved, |
| // which in turn means both the original and transferred renderer have |
| // finished their execution with regards to transferring of this |device|. |
| request->RemoveEntryInTransferMap(stream_type, transfer_id); |
| } |
| |
| if (request->IsTransferMapEmpty(stream_type) && |
| request->ShouldStopInFuture(stream_type)) { |
| StopDevice(stream_type, device->session_id()); |
| } |
| } |
| |
| void MediaStreamManager::CancelRequest( |
| DeviceRequests::const_iterator request_it) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (request_it == requests_.end()) { |
| return; |
| } |
| const std::string& label = request_it->first; |
| DeviceRequest* const request = request_it->second.get(); |
| |
| SendLogMessage( |
| base::StringPrintf("CancelRequest({label=%s})", label.c_str())); |
| |
| // This is a request for closing one or more devices. |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| const MediaRequestState state = request->state(device.type); |
| // If we have not yet requested the device to be opened - just ignore it. |
| if (state != MEDIA_REQUEST_STATE_OPENING && |
| state != MEDIA_REQUEST_STATE_DONE) { |
| continue; |
| } |
| // Stop the opening/opened devices of the requests. |
| CloseDevice(device.type, device.session_id()); |
| } |
| } |
| |
| // Cancel the request if still pending at UI side. |
| request->SetState(MediaStreamType::NUM_MEDIA_TYPES, |
| MEDIA_REQUEST_STATE_CLOSING); |
| DeleteRequest(request_it); // Invalidates |label| and |request|. |
| } |
| |
| void MediaStreamManager::DeleteRequest( |
| DeviceRequests::const_iterator request_it) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| CHECK(request_it != requests_.end()); |
| |
| SendLogMessage(base::StringPrintf("DeleteRequest([label=%s])", |
| request_it->first.c_str())); |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (request_it->second->IsGetAllScreensMedia()) { |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](GlobalRenderFrameHostId renderer_id, std::string label) { |
| GetContentClient()->browser()->NotifyMultiCaptureStateChanged( |
| renderer_id, label, |
| ContentBrowserClient::MultiCaptureChanged::kStopped); |
| }, |
| request_it->second->requesting_render_frame_host_id, |
| request_it->first)); |
| } |
| #endif |
| |
| // Clean up permission controller subscription. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &MediaStreamManager::UnsubscribeFromPermissionControllerOnUIThread, |
| request_it->second->requesting_render_frame_host_id, |
| request_it->second->audio_subscription_id, |
| request_it->second->video_subscription_id)); |
| |
| requests_.erase(request_it); |
| } |
| |
| void MediaStreamManager::ReadOutputParamsAndPostRequestToUI( |
| const std::string& label, |
| DeviceRequest* request, |
| const MediaDeviceEnumeration& enumeration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // Actual audio parameters are required only for |
| // MEDIA_GUM_TAB_AUDIO_CAPTURE. |
| if (request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE) { |
| // Using base::Unretained is safe: |audio_system_| will post |
| // PostRequestToUI() to IO thread, and MediaStreamManager is deleted on the |
| // UI thread, after the IO thread has been stopped. |
| audio_system_->GetOutputStreamParameters( |
| media::AudioDeviceDescription::kDefaultDeviceId, |
| base::BindOnce(&MediaStreamManager::PostRequestToUI, |
| base::Unretained(this), label, enumeration)); |
| } else { |
| PostRequestToUI(label, enumeration, |
| std::optional<media::AudioParameters>()); |
| } |
| } |
| |
| void MediaStreamManager::PostRequestToUI( |
| const std::string& label, |
| const MediaDeviceEnumeration& enumeration, |
| const std::optional<media::AudioParameters>& output_parameters) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!output_parameters || output_parameters->IsValid()); |
| DeviceRequest* request = FindRequest(label); |
| if (!request) { |
| return; |
| } |
| DCHECK(request->HasUIRequest()); |
| SendLogMessage( |
| base::StringPrintf("PostRequestToUI({label=%s}, ", label.c_str())); |
| |
| const MediaStreamType audio_type = request->audio_type(); |
| const MediaStreamType video_type = request->video_type(); |
| |
| // Post the request to UI and set the state. |
| if (blink::IsAudioInputMediaType(audio_type)) { |
| request->SetState(audio_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL); |
| } |
| if (blink::IsVideoInputMediaType(video_type)) { |
| request->SetState(video_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL); |
| } |
| |
| if (ShouldUseFakeUIProxy(*request)) { |
| request->ui_proxy = MakeFakeUIProxy(label, enumeration, request); |
| } else if (!request->ui_proxy) { |
| request->ui_proxy = MediaStreamUIProxy::Create(); |
| } |
| |
| request->ui_proxy->RequestAccess( |
| request->DetachUIRequest(), |
| base::BindOnce(&MediaStreamManager::HandleAccessRequestResponse, |
| base::Unretained(this), label, |
| output_parameters.value_or( |
| media::AudioParameters::UnavailableDeviceParams()))); |
| } |
| |
| void MediaStreamManager::SetUpRequest(const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const DeviceRequests::const_iterator request_it = FindRequestIterator(label); |
| if (request_it == requests_.end()) { |
| DVLOG(1) << "SetUpRequest label " << label << " doesn't exist!!"; |
| return; // This can happen if the request has been canceled. |
| } |
| DeviceRequest* const request = request_it->second.get(); |
| |
| SendLogMessage( |
| base::StringPrintf("SetUpRequest([requester_id=%d] {label=%s})", |
| request->requester_id, label.c_str())); |
| |
| request->SetAudioType(request->stream_controls().audio.stream_type); |
| request->SetVideoType(request->stream_controls().video.stream_type); |
| |
| const bool is_display_capture = |
| request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE || |
| request->video_type() == |
| MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB || |
| request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET || |
| request->audio_type() == MediaStreamType::DISPLAY_AUDIO_CAPTURE; |
| if (is_display_capture && !SetUpDisplayCaptureRequest(request)) { |
| FinalizeRequestFailed(request_it, |
| MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE); |
| return; |
| } |
| |
| const bool is_tab_capture = |
| request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE || |
| request->video_type() == MediaStreamType::GUM_TAB_VIDEO_CAPTURE; |
| if (is_tab_capture) { |
| if (!SetUpTabCaptureRequest(request, label)) { |
| FinalizeRequestFailed(request_it, |
| MediaStreamRequestResult::TAB_CAPTURE_FAILURE); |
| } |
| return; |
| } |
| |
| const bool is_screen_capture = |
| request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE; |
| if (is_screen_capture && !SetUpScreenCaptureRequest(request)) { |
| FinalizeRequestFailed(request_it, |
| MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE); |
| return; |
| } |
| |
| if (!is_tab_capture && !is_screen_capture && !is_display_capture) { |
| if (blink::IsDeviceMediaType(request->audio_type()) || |
| blink::IsDeviceMediaType(request->video_type())) { |
| StartEnumeration(request, label); |
| return; |
| } |
| // If no actual device capture is requested, set up the request with an |
| // empty device list. |
| if (!SetUpDeviceCaptureRequest(request, MediaDeviceEnumeration())) { |
| FinalizeRequestFailed(request_it, MediaStreamRequestResult::NO_HARDWARE); |
| return; |
| } |
| } |
| |
| if (request->stream_controls().request_all_screens) { |
| std::unique_ptr<media::ScreenEnumerator> screen_enumerator = |
| GetContentClient()->browser()->CreateScreenEnumerator(); |
| if (!screen_enumerator) { |
| HandleAccessRequestResponse( |
| label, media::AudioParameters(), blink::mojom::StreamDevicesSet(), |
| blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED); |
| return; |
| } |
| |
| // The screen enumerator lives on the IO thread. |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| screen_enumerator->EnumerateScreens( |
| request->video_type(), |
| base::BindOnce(&MediaStreamManager::HandleAccessRequestResponse, |
| base::Unretained(this), label, |
| media::AudioParameters())); |
| return; |
| } |
| |
| ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration()); |
| } |
| |
| bool MediaStreamManager::SetUpDisplayCaptureRequest(DeviceRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE || |
| request->video_type() == |
| MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB || |
| request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET || |
| request->audio_type() == MediaStreamType::DISPLAY_AUDIO_CAPTURE); |
| |
| // getDisplayMedia function does not permit the use of constraints for |
| // selection of a source, see |
| // https://w3c.github.io/mediacapture-screen-share/#constraints. |
| if (!request->stream_controls().video.device_ids.empty() || |
| !request->stream_controls().audio.device_ids.empty()) { |
| LOG(ERROR) << "Invalid display media request."; |
| return false; |
| } |
| |
| request->CreateUIRequest( |
| std::vector<std::string>{} /* requested_audio_device_id */, |
| std::vector<std::string>{} /* requested_video_device_id */); |
| DVLOG(3) << "Audio requested " << request->stream_controls().audio.requested() |
| << " Video requested " |
| << request->stream_controls().video.requested(); |
| return true; |
| } |
| |
| bool MediaStreamManager::SetUpDeviceCaptureRequest( |
| DeviceRequest* request, |
| const MediaDeviceEnumeration& enumeration) { |
| DCHECK((request->audio_type() == MediaStreamType::DEVICE_AUDIO_CAPTURE || |
| request->audio_type() == MediaStreamType::NO_SERVICE) && |
| (request->video_type() == MediaStreamType::DEVICE_VIDEO_CAPTURE || |
| request->video_type() == MediaStreamType::NO_SERVICE)); |
| SendLogMessage(base::StringPrintf( |
| "SetUpDeviceCaptureRequest([requester_id=%d])", request->requester_id)); |
| std::vector<std::string> audio_device_ids; |
| if (request->stream_controls().audio.requested() && |
| !GetEligibleCaptureDeviceids( |
| request, request->audio_type(), |
| enumeration[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)], |
| &audio_device_ids)) { |
| return false; |
| } |
| |
| std::vector<std::string> video_device_ids; |
| if (request->stream_controls().video.requested() && |
| !GetEligibleCaptureDeviceids( |
| request, request->video_type(), |
| enumeration[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)], |
| &video_device_ids)) { |
| return false; |
| } |
| request->CreateUIRequest(audio_device_ids, video_device_ids); |
| DVLOG(3) << "Audio requested " << request->stream_controls().audio.requested() |
| << " device id = " << audio_device_ids.size() << "Video requested " |
| << request->stream_controls().video.requested() |
| << " device id = " << video_device_ids.size(); |
| return true; |
| } |
| |
| bool MediaStreamManager::SetUpTabCaptureRequest(DeviceRequest* request, |
| const std::string& label) { |
| DCHECK(request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE || |
| request->video_type() == MediaStreamType::GUM_TAB_VIDEO_CAPTURE); |
| |
| std::string capture_device_id; |
| if (!request->stream_controls().audio.device_ids.empty() && |
| !request->stream_controls().audio.device_ids.front().empty()) { |
| capture_device_id = request->stream_controls().audio.device_ids.front(); |
| } else if (!request->stream_controls().video.device_ids.empty()) { |
| capture_device_id = request->stream_controls().video.device_ids.front(); |
| } else { |
| return false; |
| } |
| |
| if ((request->audio_type() != MediaStreamType::GUM_TAB_AUDIO_CAPTURE && |
| request->audio_type() != MediaStreamType::NO_SERVICE) || |
| (request->video_type() != MediaStreamType::GUM_TAB_VIDEO_CAPTURE && |
| request->video_type() != MediaStreamType::NO_SERVICE)) { |
| return false; |
| } |
| |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::ResolveTabCaptureDeviceIdOnUIThread, |
| base::Unretained(this), capture_device_id, |
| request->requesting_render_frame_host_id, |
| request->salt_and_origin.origin().GetURL()), |
| base::BindOnce( |
| &MediaStreamManager::FinishTabCaptureRequestSetupWithDeviceId, |
| base::Unretained(this), label)); |
| return true; |
| } |
| |
| DesktopMediaID MediaStreamManager::ResolveTabCaptureDeviceIdOnUIThread( |
| const std::string& capture_device_id, |
| |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| const GURL& origin) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // Resolve DesktopMediaID for the specified device id. |
| return DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId( |
| capture_device_id, requesting_render_frame_host_id.child_id, |
| requesting_render_frame_host_id.frame_routing_id, |
| url::Origin::Create(origin), kRegistryStreamTypeTab); |
| } |
| |
| void MediaStreamManager::FinishTabCaptureRequestSetupWithDeviceId( |
| const std::string& label, |
| const DesktopMediaID& device_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
| |
| const DeviceRequests::const_iterator request_it = FindRequestIterator(label); |
| if (request_it == requests_.end()) { |
| DVLOG(1) << "SetUpRequest label " << label << " doesn't exist!!"; |
| return; // This can happen if the request has been canceled. |
| } |
| DeviceRequest* const request = request_it->second.get(); |
| |
| // Received invalid device id. |
| if (device_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS) { |
| FinalizeRequestFailed(request_it, |
| MediaStreamRequestResult::TAB_CAPTURE_FAILURE); |
| return; |
| } |
| |
| content::WebContentsMediaCaptureId web_id = device_id.web_contents_id; |
| web_id.disable_local_echo = request->stream_controls().disable_local_echo; |
| |
| request->tab_capture_device_id = web_id.ToString(); |
| |
| request->CreateTabCaptureUIRequest( |
| {web_id.render_process_id, web_id.main_render_frame_id}); |
| |
| DVLOG(3) << "SetUpTabCaptureRequest " |
| << ", {capture_device_id = " << web_id.ToString() << "}" |
| << ", {target_render_process_id = " << web_id.render_process_id |
| << "}" |
| << ", {target_render_frame_id = " << web_id.main_render_frame_id |
| << "}"; |
| |
| ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration()); |
| } |
| |
| bool MediaStreamManager::SetUpScreenCaptureRequest(DeviceRequest* request) { |
| DCHECK(request->audio_type() == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE || |
| request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE); |
| |
| // For screen capture we only support two valid combinations: |
| // (1) screen video capture only, or |
| // (2) screen video capture with loopback audio capture. |
| if (request->video_type() != MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE || |
| (request->audio_type() != MediaStreamType::NO_SERVICE && |
| request->audio_type() != MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE)) { |
| LOG(ERROR) << "Invalid screen capture request."; |
| return false; |
| } |
| |
| std::vector<std::string> video_device_ids; |
| if (request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE && |
| !request->stream_controls().video.device_ids.empty()) { |
| video_device_ids = request->stream_controls().video.device_ids; |
| } |
| |
| const std::vector<std::string> audio_device_ids = |
| request->audio_type() == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE |
| ? video_device_ids |
| : std::vector<std::string>{}; |
| |
| request->CreateUIRequest(audio_device_ids, video_device_ids); |
| return true; |
| } |
| |
| void MediaStreamManager::SetUpDesktopCaptureChangeSourceRequest( |
| DeviceRequest* request, |
| const std::string& label, |
| const DesktopMediaID& media_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(blink::IsDesktopCaptureMediaType(request->video_type())); |
| DCHECK(request->request_type() == blink::MEDIA_GENERATE_STREAM || |
| request->request_type() == blink::MEDIA_DEVICE_UPDATE); |
| |
| // Set up request type to bring up the picker again within a session. |
| request->set_request_type(blink::MEDIA_DEVICE_UPDATE); |
| |
| request->CreateUIRequest( |
| std::vector<std::string>{} /* requested_audio_device_id */, |
| media_id.is_null() |
| ? std::vector<std::string>{} |
| : std::vector{media_id.ToString()} /* requested_video_device_id */); |
| |
| ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration()); |
| } |
| |
| MediaStreamDevices MediaStreamManager::GetDevicesOpenedByRequest( |
| const std::string& label) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DeviceRequest* request = FindRequest(label); |
| if (!request) { |
| return MediaStreamDevices(); |
| } |
| return blink::ToMediaStreamDevicesList(request->stream_devices_set); |
| } |
| |
| void MediaStreamManager::GetRawDeviceIdsOpenedForFrame( |
| RenderFrameHost* render_frame_host, |
| blink::mojom::MediaStreamType type, |
| GetRawDeviceIdsOpenedForFrameCallback callback) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::flat_set<GlobalRenderFrameHostId> all_render_frame_host_ids; |
| render_frame_host->ForEachRenderFrameHost( |
| [&all_render_frame_host_ids](RenderFrameHost* render_frame_host) { |
| all_render_frame_host_ids.insert(render_frame_host->GetGlobalId()); |
| }); |
| |
| GetIOThreadTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::GetRawDeviceIdsOpenedForFrameIds, |
| base::Unretained(this), type, std::move(callback), |
| std::move(all_render_frame_host_ids))); |
| } |
| |
| void MediaStreamManager::GetRawDeviceIdsOpenedForFrameIds( |
| blink::mojom::MediaStreamType type, |
| GetRawDeviceIdsOpenedForFrameCallback callback, |
| base::flat_set<GlobalRenderFrameHostId> render_frame_host_ids) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| std::vector<std::string> device_ids; |
| for (const auto& [label, request] : requests_) { |
| if (request->state(type) != MediaRequestState::MEDIA_REQUEST_STATE_DONE) { |
| continue; |
| } |
| |
| if (!render_frame_host_ids.contains( |
| request->requesting_render_frame_host_id)) { |
| continue; |
| } |
| |
| if (request->audio_type() == type && request->audio_raw_id()) { |
| device_ids.push_back(request->audio_raw_id().value()); |
| } else if (request->video_type() == type && request->video_raw_id()) { |
| device_ids.push_back(request->video_raw_id().value()); |
| } |
| } |
| |
| std::move(callback).Run(device_ids); |
| } |
| |
| bool MediaStreamManager::FindExistingRequestedDevice( |
| const DeviceRequest& new_request, |
| const MediaStreamDevice& new_device, |
| MediaStreamDevice* existing_device, |
| MediaRequestState* existing_request_state) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(existing_device); |
| DCHECK(existing_request_state); |
| |
| std::string hashed_source_id = |
| GetHMACForRawMediaDeviceID(new_request.salt_and_origin, new_device.id); |
| |
| bool is_audio_capture = |
| new_device.type == MediaStreamType::DEVICE_AUDIO_CAPTURE && |
| new_request.audio_type() == MediaStreamType::DEVICE_AUDIO_CAPTURE; |
| const auto& audio_stream_selection_info = |
| new_request.audio_stream_selection_info_ptr; |
| std::optional<base::UnguessableToken> requested_session_id = std::nullopt; |
| if (is_audio_capture && |
| audio_stream_selection_info->is_search_by_session_id() && |
| !audio_stream_selection_info->get_search_by_session_id().is_null()) { |
| const auto& session_id_map = |
| audio_stream_selection_info->get_search_by_session_id()->session_id_map; |
| if (!session_id_map.contains(hashed_source_id)) { |
| return false; |
| } |
| requested_session_id = session_id_map.at(hashed_source_id); |
| } |
| |
| for (const LabeledDeviceRequest& labeled_request : requests_) { |
| const DeviceRequest* request = labeled_request.second.get(); |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| if (request->requesting_render_frame_host_id == |
| new_request.requesting_render_frame_host_id && |
| request->request_type() == new_request.request_type()) { |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| const bool is_same_device = |
| device.id == hashed_source_id && device.type == new_device.type; |
| // If `audio_stream_selection_info` is `search_only_by_device_id`, the |
| // search is performed only based on the `device.id`. If, however, |
| // `audio_stream_selection_info` is `session_id_map`, the |
| // search also includes the session ID provided in the request. |
| // NB: this only applies to audio. In case of media stream types that |
| // are not an audio capture, the session id is always ignored. |
| const bool is_same_session = |
| !is_audio_capture || |
| audio_stream_selection_info->is_search_only_by_device_id() || |
| device.session_id() == *requested_session_id; |
| |
| if (is_same_device && is_same_session) { |
| *existing_device = device; |
| // Make sure that the audio |effects| reflect what the request |
| // is set to and not what the capabilities are. |
| int effects = existing_device->input.effects(); |
| FilterAudioEffects(request->stream_controls(), &effects); |
| EnableHotwordEffect(request->stream_controls(), &effects); |
| existing_device->input.set_effects(effects); |
| *existing_request_state = request->state(device.type); |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| void MediaStreamManager::FinalizeGenerateStreams(const std::string& label, |
| DeviceRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request); |
| DCHECK_EQ(request->request_type(), blink::MEDIA_GENERATE_STREAM); |
| SendLogMessage( |
| base::StringPrintf("FinalizeGenerateStreams({label=%s}, {requester_id=" |
| "%d}, {request_type=%s})", |
| label.c_str(), request->requester_id, |
| RequestTypeToString(request->request_type()))); |
| |
| // Subscribe to follow permission changes in order to close streams when the |
| // user denies mic/camera. Only done for input devices. |
| if (blink::IsDeviceMediaType(request->audio_type()) || |
| blink::IsDeviceMediaType(request->video_type())) { |
| SubscribeToPermissionController(label, request); |
| } |
| |
| blink::mojom::StreamDevicesSetPtr stream_devices_set = |
| request->stream_devices_set.Clone(); |
| |
| if (request->IsGetAllScreensMedia()) { |
| PanTiltZoomPermissionChecked(label, MediaStreamDevice(), |
| /*pan_tilt_zoom_allowed=*/false); |
| return; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| CHECK(!request->captured_surface_controller()); |
| const WebContentsMediaCaptureId captured_tab_id = request->GetCapturedTabId(); |
| if (!captured_tab_id.is_null()) { |
| request->SetCapturedSurfaceController( |
| captured_surface_controller_factory_.Run( |
| request->requesting_render_frame_host_id, captured_tab_id, |
| base::BindRepeating(&DeviceRequest::OnZoomLevelChange, |
| request->GetWeakPtr(), label))); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| // TODO(crbug.com/40216442): Generalize to multiple streams. |
| DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size()); |
| |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop and so outlives the IO thread. |
| // TODO(crbug.com/40833062): Avoid using PTZ permission checks for non-gUM |
| // tracks. |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&MediaDevicesPermissionChecker:: |
| HasPanTiltZoomPermissionGrantedOnUIThread, |
| request->requesting_render_frame_host_id.child_id, |
| request->requesting_render_frame_host_id.frame_routing_id), |
| base::BindOnce( |
| &MediaStreamManager::PanTiltZoomPermissionChecked, |
| base::Unretained(this), label, |
| request->stream_devices_set.stream_devices[0]->video_device)); |
| } |
| |
| void MediaStreamManager::FinalizeGetOpenDevice(const std::string& label, |
| DeviceRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request); |
| DCHECK_EQ(request->request_type(), blink::MEDIA_GET_OPEN_DEVICE); |
| SendLogMessage( |
| base::StringPrintf("FinalizeGetOpenDevice({label=%s}, {requester_id=" |
| "%d}, {request_type=%s})", |
| label.c_str(), request->requester_id, |
| RequestTypeToString(request->request_type()))); |
| |
| // Subscribe to follow permission changes in order to close streams when the |
| // user denies mic/camera. Only done for input devices. |
| if (blink::IsDeviceMediaType(request->audio_type()) || |
| blink::IsDeviceMediaType(request->video_type())) { |
| SubscribeToPermissionController(label, request); |
| } |
| |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop and so outlives the IO thread. |
| // TODO(crbug.com/40833063): Avoid this check once you have this permission |
| // value from original context. |
| GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&MediaDevicesPermissionChecker:: |
| HasPanTiltZoomPermissionGrantedOnUIThread, |
| request->requesting_render_frame_host_id.child_id, |
| request->requesting_render_frame_host_id.frame_routing_id), |
| base::BindOnce( |
| &MediaStreamManager::PanTiltZoomPermissionChecked, |
| base::Unretained(this), label, |
| request->stream_devices_set.stream_devices[0]->video_device)); |
| } |
| |
| // TODO(crbug.com/40058526): Ensure CaptureHandle works for transferred |
| // MediaStreamTracks and add tests for the same. |
| // TODO(crbug.com/40832991): Ensure track transfer does not initiate |
| // focus-change with Conditional focus enabled. |
| void MediaStreamManager::PanTiltZoomPermissionChecked( |
| const std::string& label, |
| const std::optional<MediaStreamDevice>& video_device, |
| bool pan_tilt_zoom_allowed) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DeviceRequest* request = FindRequest(label); |
| if (!request) { |
| return; |
| } |
| |
| SendLogMessage(base::StringPrintf( |
| "PanTiltZoomPermissionChecked({label=%s}, {requester_id=" |
| "%d}, {request_type=%s}, {pan_tilt_zoom_allowed=%d})", |
| label.c_str(), request->requester_id, |
| RequestTypeToString(request->request_type()), pan_tilt_zoom_allowed)); |
| |
| request->PanTiltZoomPermissionChecked(label, pan_tilt_zoom_allowed); |
| |
| if (request->IsGetAllScreensMedia()) { |
| return; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| // 1. Only the first call to SetCapturedDisplaySurfaceFocus() has an |
| // effect, so a direct call to SetCapturedDisplaySurfaceFocus() |
| // before the scheduled task is executed would render the scheduled |
| // task ineffectual (by design). |
| // If conditional-focus is enabled in Blink, the application might |
| // suppress this focus-change by calling focus(false). Otherwise, |
| // either this following task changes focus in 1s, or the microtask |
| // that Blink schedules does so even sooner. |
| // 2. Using base::Unretained is safe since MediaStreamManager is deleted on |
| // the UI thread, after the IO thread has been stopped. |
| GetIOThreadTaskRunner({})->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::SetCapturedDisplaySurfaceFocus, |
| base::Unretained(this), label, /*focus=*/true, |
| /*is_from_microtask=*/false, |
| /*is_from_timer=*/true), |
| conditional_focus_window_); |
| #endif |
| |
| // We only start tracking once stream generation is truly complete. |
| // If the CaptureHandle observable by this capturer has changed asynchronously |
| // while the current task was hopping between threads/queues, an event will |
| // be fired by the CaptureHandleManager. |
| if (video_device.has_value()) { |
| MaybeStartTrackingCaptureHandleConfig(label, video_device.value(), |
| *request); |
| } |
| } |
| |
| void MediaStreamManager::FinalizeRequestFailed( |
| DeviceRequests::const_iterator request_it, |
| MediaStreamRequestResult result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| CHECK(request_it != requests_.end()); |
| |
| DeviceRequest* const request = request_it->second.get(); |
| |
| SendLogMessage(base::StringPrintf( |
| "FinalizeRequestFailed({label=%s}, {requester_id=%d}, {result=%s})", |
| request_it->first.c_str(), request->requester_id, |
| RequestResultToString(result))); |
| |
| switch (request->request_type()) { |
| case blink::MEDIA_DEVICE_ACCESS: |
| case blink::MEDIA_GENERATE_STREAM: |
| case blink::MEDIA_GET_OPEN_DEVICE: |
| case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: { |
| request->FinalizeRequestFailed(result); |
| break; |
| } |
| case blink::MEDIA_DEVICE_UPDATE: { |
| // Fail to change capture source, keep everything unchanged and |
| // bring the previous shared tab to the front. |
| DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size()); |
| const blink::mojom::StreamDevices& devices = |
| *request->stream_devices_set.stream_devices[0]; |
| if (devices.video_device.has_value()) { |
| const blink::MediaStreamDevice& device = devices.video_device.value(); |
| DCHECK_NE(device.type, MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET); |
| // TODO(crbug.com/40228114): Also consider |
| // DISPLAY_VIDEO_CAPTURE_THIS_TAB. |
| if (device.type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE || |
| device.type == MediaStreamType::DISPLAY_VIDEO_CAPTURE) { |
| DesktopMediaID source = DesktopMediaID::Parse(device.id); |
| DCHECK(source.type == DesktopMediaID::TYPE_WEB_CONTENTS); |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::ActivateTabOnUIThread, |
| base::Unretained(this), source)); |
| } |
| } |
| return; |
| } |
| default: |
| NOTREACHED(); |
| } |
| |
| DeleteRequest(request_it); |
| } |
| |
| void MediaStreamManager::FinalizeChangeDevice(const std::string& label, |
| DeviceRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request); |
| |
| request->FinalizeChangeDevice(label); |
| |
| MaybeUpdateTrackedCaptureHandleConfigs(label, request->stream_devices_set, |
| *request); |
| } |
| |
| void MediaStreamManager::FinalizeMediaAccessRequest( |
| DeviceRequests::const_iterator request_it, |
| const blink::mojom::StreamDevicesSet& stream_devices_set) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| CHECK(request_it != requests_.end()); |
| DeviceRequest* const request = request_it->second.get(); |
| |
| request->FinalizeMediaAccessRequest(request_it->first, stream_devices_set); |
| |
| // Delete the request since it is done. |
| DeleteRequest(request_it); |
| } |
| |
| void MediaStreamManager::SetRequestDevice( |
| blink::mojom::StreamDevices& target_devices, |
| const blink::MediaStreamDevice& device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (blink::IsAudioInputMediaType(device.type)) { |
| target_devices.audio_device = device; |
| } else { |
| DCHECK(blink::IsVideoInputMediaType(device.type)); |
| target_devices.video_device = device; |
| } |
| } |
| |
| void MediaStreamManager::InitializeMaybeAsync( |
| std::unique_ptr<VideoCaptureProvider> video_capture_provider) { |
| // Some unit tests initialize the MSM in the IO thread and assume the |
| // initialization is done synchronously. Other clients call this from a |
| // different thread and expect initialization to run asynchronously. |
| if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&MediaStreamManager::InitializeMaybeAsync, |
| base::Unretained(this), |
| std::move(video_capture_provider))); |
| return; |
| } |
| SendLogMessage(base::StringPrintf("InitializeMaybeAsync([this=%p])", this)); |
| |
| // Store a pointer to |this| on the IO thread to avoid having to jump to |
| // the UI thread to fetch a pointer to the MSM. In particular on Android, |
| // it can be problematic to post to a UI thread from arbitrary worker |
| // threads since attaching to the VM is required and we may have to access |
| // the MSM from callback threads that we don't own and don't want to |
| // attach. |
| media_stream_manager = this; |
| |
| audio_input_device_manager_ = |
| base::MakeRefCounted<AudioInputDeviceManager>(audio_system_); |
| audio_input_device_manager_->RegisterListener(this); |
| |
| // We want to be notified of IO message loop destruction to delete the thread |
| // and the device managers. |
| base::CurrentThread::Get()->AddDestructionObserver(this); |
| |
| video_capture_manager_ = base::MakeRefCounted<VideoCaptureManager>( |
| std::move(video_capture_provider), |
| base::BindRepeating(&SendVideoCaptureLogMessage)); |
| video_capture_manager_->RegisterListener(this); |
| |
| // Using base::Unretained(this) is safe because |this| owns and therefore |
| // outlives |media_devices_manager_|. |
| media_devices_manager_ = std::make_unique<MediaDevicesManager>( |
| audio_system_, video_capture_manager_, |
| base::BindRepeating(&MediaStreamManager::StopRemovedDevice, |
| base::Unretained(this)), |
| base::BindRepeating(&MediaStreamManager::NotifyDevicesChanged, |
| base::Unretained(this))); |
| |
| if (base::FeatureList::IsEnabled( |
| blink::features::kPreferredAudioOutputDevices)) { |
| preferred_audio_output_device_manager_ = |
| std::make_unique<PreferredAudioOutputDeviceManagerImpl>(); |
| } |
| } |
| |
| void MediaStreamManager::Opened( |
| MediaStreamType stream_type, |
| const base::UnguessableToken& capture_session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf("Opened({stream_type=%s}, {session_id=%s})", |
| StreamTypeToString(stream_type), |
| capture_session_id.ToString().c_str())); |
| |
| // Find the request(s) containing this device and mark it as used. |
| // It can be used in several requests since the same device can be |
| // requested from the same web page. |
| for (const LabeledDeviceRequest& labeled_request : requests_) { |
| const std::string& label = labeled_request.first; |
| DeviceRequest* request = labeled_request.second.get(); |
| |
| if (request->stream_devices_set.stream_devices.empty()) { |
| continue; |
| } |
| |
| // It can happen that a previous stream already failed and set an error, |
| // in which case this streams request does not need to be handled further. |
| if (request->state(stream_type) == MEDIA_REQUEST_STATE_ERROR) { |
| continue; |
| } |
| |
| for (blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| |
| blink::MediaStreamDevice& device = device_ptr->value(); |
| if (device.type == stream_type && |
| device.session_id() == capture_session_id) { |
| if (request->state(device.type) == MEDIA_REQUEST_STATE_DONE) { |
| continue; |
| } |
| |
| // We've found a matching request. |
| CHECK_EQ(request->state(device.type), MEDIA_REQUEST_STATE_OPENING); |
| request->SetDeviceOpened(device.type); |
| if (request->devices_opened_count(device.type) == |
| request->stream_devices_set.stream_devices.size()) { |
| request->SetState(device.type, MEDIA_REQUEST_STATE_DONE); |
| request->ResetDevicesOpened(device.type); |
| } |
| |
| if (blink::IsAudioInputMediaType(device.type)) { |
| // Store the native audio parameters in the device struct. |
| // TODO(xians): Handle the tab capture sample rate/channel layout |
| // in AudioInputDeviceManager::Open(). |
| if (device.type != MediaStreamType::GUM_TAB_AUDIO_CAPTURE) { |
| const MediaStreamDevice* opened_device = |
| audio_input_device_manager_->GetOpenedDeviceById( |
| device.session_id()); |
| device.input = opened_device->input; |
| |
| // Since the audio input device manager will set the input |
| // parameters to the default settings (including supported |
| // effects), we need to adjust those settings here according to |
| // what the request asks for. |
| int effects = device.input.effects(); |
| FilterAudioEffects(request->stream_controls(), &effects); |
| EnableHotwordEffect(request->stream_controls(), &effects); |
| device.input.set_effects(effects); |
| } |
| } |
| if (RequestDone(*request)) { |
| HandleRequestDone(label, request); |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| void MediaStreamManager::HandleRequestDone(const std::string& label, |
| DeviceRequest* request) { |
| DCHECK(RequestDone(*request)); |
| |
| switch (request->request_type()) { |
| case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY: |
| request->FinalizeRequest(label); |
| OnStreamStarted(label); |
| break; |
| case blink::MEDIA_GENERATE_STREAM: { |
| FinalizeGenerateStreams(label, request); |
| OnStreamStarted(label); |
| break; |
| } |
| case blink::MEDIA_GET_OPEN_DEVICE: { |
| FinalizeGetOpenDevice(label, request); |
| break; |
| } |
| case blink::MEDIA_DEVICE_UPDATE: |
| FinalizeChangeDevice(label, request); |
| OnStreamStarted(label); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void MediaStreamManager::Closed( |
| MediaStreamType stream_type, |
| const base::UnguessableToken& capture_session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf("Closed({stream_type=%s}, {session_id=%s})", |
| StreamTypeToString(stream_type), |
| capture_session_id.ToString().c_str())); |
| } |
| |
| void MediaStreamManager::DevicesEnumerated( |
| bool requested_audio_input, |
| bool requested_video_input, |
| const std::string& label, |
| const MediaDeviceEnumeration& enumeration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const DeviceRequests::const_iterator request_it = FindRequestIterator(label); |
| if (request_it == requests_.end()) { |
| return; |
| } |
| DeviceRequest* const request = request_it->second.get(); |
| |
| SendLogMessage(base::StringPrintf( |
| "DevicesEnumerated({label=%s}, {requester_id=%d}, {request_type=%s})", |
| label.c_str(), request->requester_id, |
| RequestTypeToString(request->request_type()))); |
| |
| const auto requested = |
| std::to_array<bool>({requested_audio_input, requested_video_input}); |
| const auto stream_types = |
| std::to_array<MediaStreamType>({MediaStreamType::DEVICE_AUDIO_CAPTURE, |
| MediaStreamType::DEVICE_VIDEO_CAPTURE}); |
| for (size_t i = 0; i < std::size(requested); ++i) { |
| if (!requested[i]) { |
| continue; |
| } |
| |
| DCHECK(request->audio_type() == stream_types[i] || |
| request->video_type() == stream_types[i]); |
| if (request->state(stream_types[i]) == MEDIA_REQUEST_STATE_REQUESTED) { |
| request->SetState(stream_types[i], MEDIA_REQUEST_STATE_PENDING_APPROVAL); |
| } |
| } |
| |
| if (!SetUpDeviceCaptureRequest(request, enumeration)) { |
| FinalizeRequestFailed(request_it, MediaStreamRequestResult::NO_HARDWARE); |
| } else { |
| ReadOutputParamsAndPostRequestToUI(label, request, enumeration); |
| } |
| } |
| |
| void MediaStreamManager::Aborted( |
| MediaStreamType stream_type, |
| const base::UnguessableToken& capture_session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf( |
| "Aborted({stream_type=%s}, {session_id=%s})", |
| StreamTypeToString(stream_type), capture_session_id.ToString().c_str())); |
| StopDevice(stream_type, capture_session_id); |
| } |
| |
| void MediaStreamManager::UseFakeUIFactoryForTests( |
| base::RepeatingCallback<std::unique_ptr<FakeMediaStreamUIProxy>(void)> |
| fake_ui_factory, |
| bool use_for_gum_desktop_capture, |
| std::optional<WebContentsMediaCaptureId> captured_tab_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| fake_ui_factory_ = std::move(fake_ui_factory); |
| use_fake_ui_for_gum_desktop_capture_ = use_for_gum_desktop_capture; |
| fake_ui_factory_captured_tab_id_ = captured_tab_id; |
| } |
| |
| // static |
| void MediaStreamManager::RegisterNativeLogCallback( |
| int renderer_host_id, |
| base::RepeatingCallback<void(const std::string&)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!media_stream_manager) { |
| DLOG(ERROR) << "No MediaStreamManager on the IO thread."; |
| return; |
| } |
| |
| media_stream_manager->DoNativeLogCallbackRegistration(renderer_host_id, |
| std::move(callback)); |
| } |
| |
| // static |
| void MediaStreamManager::UnregisterNativeLogCallback(int renderer_host_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!media_stream_manager) { |
| DLOG(ERROR) << "No MediaStreamManager on the IO thread."; |
| return; |
| } |
| |
| media_stream_manager->DoNativeLogCallbackUnregistration(renderer_host_id); |
| } |
| |
| void MediaStreamManager::AddLogMessageOnIOThread(const std::string& message) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (const auto& callback : log_callbacks_) { |
| callback.second.Run(message); |
| } |
| } |
| |
| void MediaStreamManager::HandleAccessRequestResponse( |
| const std::string& label, |
| const media::AudioParameters& output_parameters, |
| const blink::mojom::StreamDevicesSet& stream_devices_set, |
| MediaStreamRequestResult result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK((result == MediaStreamRequestResult::OK && |
| !stream_devices_set.stream_devices.empty()) || |
| (result != MediaStreamRequestResult::OK && |
| stream_devices_set.stream_devices.empty())); |
| |
| const DeviceRequests::const_iterator request_it = FindRequestIterator(label); |
| if (request_it == requests_.end()) { |
| // The request has been canceled before the UI returned. |
| return; |
| } |
| DeviceRequest* const request = request_it->second.get(); |
| |
| SendLogMessage(base::StringPrintf( |
| "HandleAccessRequestResponse({label=%s}, {request=%s}, {result=%s})", |
| label.c_str(), RequestTypeToString(request->request_type()), |
| RequestResultToString(result))); |
| |
| media_stream_metrics::RecordMediaStreamRequestResponseMetric( |
| request->video_type(), request->request_type(), result); |
| |
| if (request->salt_and_origin.ukm_source_id().has_value()) { |
| media_stream_metrics::RecordMediaStreamRequestResponseUKM( |
| request->salt_and_origin.ukm_source_id().value(), request->video_type(), |
| request->request_type(), result); |
| } |
| |
| if (request->request_type() == blink::MEDIA_DEVICE_ACCESS) { |
| FinalizeMediaAccessRequest(request_it, stream_devices_set); |
| return; |
| } |
| |
| // Handle the case when the request was denied. |
| if (result != MediaStreamRequestResult::OK) { |
| FinalizeRequestFailed(request_it, result); |
| return; |
| } |
| |
| DCHECK(std::ranges::all_of( |
| stream_devices_set.stream_devices, |
| [](const blink::mojom::StreamDevicesPtr& stream_devices) { |
| return stream_devices->audio_device.has_value() || |
| stream_devices->video_device.has_value(); |
| })); |
| |
| if (request->request_type() == blink::MEDIA_DEVICE_UPDATE) { |
| HandleChangeSourceRequestResponse(label, request, stream_devices_set); |
| return; |
| } |
| |
| // Process all newly-accepted devices for this request. |
| bool found_audio = false; |
| bool found_video = false; |
| for (size_t stream_index = request->stream_devices_set.stream_devices.size(); |
| stream_index < stream_devices_set.stream_devices.size(); |
| ++stream_index) { |
| request->stream_devices_set.stream_devices.push_back( |
| blink::mojom::StreamDevices::New()); |
| } |
| for (size_t stream_index = 0; |
| stream_index < stream_devices_set.stream_devices.size(); |
| ++stream_index) { |
| const blink::mojom::StreamDevicesPtr& stream_devices_ptr = |
| stream_devices_set.stream_devices[stream_index]; |
| const blink::mojom::StreamDevices& devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : |
| {&devices.audio_device, &devices.video_device}) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| MediaStreamDevice device = device_ptr->value(); |
| |
| if (device.type == MediaStreamType::GUM_TAB_VIDEO_CAPTURE || |
| device.type == MediaStreamType::GUM_TAB_AUDIO_CAPTURE) { |
| device.id = request->tab_capture_device_id; |
| } |
| |
| // Initialize the sample_rate and channel_layout here since for audio |
| // mirroring, we don't go through EnumerateDevices where these are usually |
| // initialized. |
| if (device.type == MediaStreamType::GUM_TAB_AUDIO_CAPTURE || |
| device.type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE) { |
| int sample_rate = output_parameters.sample_rate(); |
| // If we weren't able to get the native sampling rate or the sample_rate |
| // is outside the valid range for input devices set reasonable defaults. |
| if (sample_rate <= 0 || sample_rate > 96000) { |
| sample_rate = 44100; |
| } |
| |
| media::AudioParameters params( |
| device.input.format(), media::ChannelLayoutConfig::Stereo(), |
| sample_rate, device.input.frames_per_buffer()); |
| params.set_effects(device.input.effects()); |
| params.set_mic_positions(device.input.mic_positions()); |
| DCHECK(params.IsValid()); |
| device.input = params; |
| } |
| |
| if (device.type == request->audio_type()) { |
| found_audio = true; |
| } else if (device.type == request->video_type()) { |
| found_video = true; |
| } |
| |
| // If this is request for a new MediaStream, a device is only opened once |
| // per render frame. This is so that the permission to use a device can be |
| // revoked by a single call to StopStreamDevice regardless of how many |
| // MediaStreams it is being used in. |
| if (request->request_type() == blink::MEDIA_GENERATE_STREAM) { |
| MediaRequestState state; |
| if (FindExistingRequestedDevice(*request, device, &device, &state)) { |
| SetRequestDevice( |
| *request->stream_devices_set.stream_devices[stream_index], |
| device); |
| request->SetState(device.type, state); |
| SendLogMessage(base::StringPrintf( |
| "HandleAccessRequestResponse([label=%s]) => " |
| "(already opened device: [id: %s, session_id: %s])", |
| label.c_str(), device.id.c_str(), |
| device.session_id().ToString().c_str())); |
| continue; |
| } |
| } |
| device.set_session_id(GetDeviceManager(device.type)->Open(device)); |
| if (device.type == request->audio_type()) { |
| request->SetAudioRawId(device.id); |
| } else if (device.type == request->video_type()) { |
| request->SetVideoRawId(device.id); |
| } |
| TranslateDeviceIdToSourceId(request, &device); |
| SetRequestDevice( |
| *request->stream_devices_set.stream_devices[stream_index], device); |
| const MediaRequestState current_state = request->state(device.type); |
| if (current_state != MEDIA_REQUEST_STATE_OPENING && |
| current_state != MEDIA_REQUEST_STATE_ERROR) { |
| request->SetState(device.type, MEDIA_REQUEST_STATE_OPENING); |
| } |
| SendLogMessage( |
| base::StringPrintf("HandleAccessRequestResponse([label=%s]) => " |
| "(opening device: [id: %s, session_id: %s])", |
| label.c_str(), device.id.c_str(), |
| device.session_id().ToString().c_str())); |
| } |
| } |
| |
| // If the user does not choose to share audio, the audio device is not |
| // added. In this case the audio type needs to be set to NO_SERVICE so that no |
| // audio device is added if a change-source is requested (i.e., if the |
| // share-this-tab-instead button is clicked). (Resolves crbug.com/1378910) |
| if (!found_audio) { |
| request->DisableAudioSharing(); |
| } |
| |
| // Check whether we've received all stream types requested. |
| if (!found_audio && blink::IsAudioInputMediaType(request->audio_type())) { |
| request->SetState(request->audio_type(), MEDIA_REQUEST_STATE_ERROR); |
| DVLOG(1) << "Set no audio found label " << label; |
| } |
| |
| if (!found_video && blink::IsVideoInputMediaType(request->video_type())) { |
| request->SetState(request->video_type(), MEDIA_REQUEST_STATE_ERROR); |
| } |
| |
| if (RequestDone(*request)) { |
| HandleRequestDone(label, request); |
| } |
| } |
| |
| void MediaStreamManager::HandleChangeSourceRequestResponse( |
| const std::string& label, |
| DeviceRequest* request, |
| const blink::mojom::StreamDevicesSet& stream_devices_set) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK_EQ(request->stream_devices_set.stream_devices.size(), 1u); |
| DCHECK_LE(request->old_stream_devices_set.stream_devices.size(), 1u); |
| DCHECK_EQ(stream_devices_set.stream_devices.size(), 1u); |
| |
| DVLOG(1) << "HandleChangeSourceRequestResponse(" |
| << ", {label = " << label << "})"; |
| |
| if (request->old_stream_devices_set.stream_devices.empty()) { |
| request->old_stream_devices_set.stream_devices.emplace_back( |
| blink::mojom::StreamDevices::New()); |
| } |
| std::swap(request->old_stream_devices_set.stream_devices, |
| request->stream_devices_set.stream_devices); |
| |
| const blink::mojom::StreamDevices& devices = |
| *stream_devices_set.stream_devices[0]; |
| for (const std::optional<blink::MediaStreamDevice>* device : |
| {&devices.audio_device, &devices.video_device}) { |
| if (!device->has_value()) { |
| continue; |
| } |
| blink::MediaStreamDevice new_device = device->value(); |
| new_device.set_session_id( |
| GetDeviceManager(new_device.type)->Open(new_device)); |
| request->SetState(new_device.type, MEDIA_REQUEST_STATE_OPENING); |
| SetRequestDevice(*request->stream_devices_set.stream_devices[0], |
| new_device); |
| } |
| |
| request->SetAudioType(devices.audio_device.has_value() |
| ? request->stream_controls().audio.stream_type |
| : MediaStreamType::NO_SERVICE); |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| if (CapturedSurfaceController* const captured_surface_controller = |
| request->captured_surface_controller()) { |
| // Either inform the controller that it's now controlling a new tab, |
| // or neutralize it if it's no longer capturing a tab. |
| captured_surface_controller->UpdateCaptureTarget( |
| request->GetCapturedTabId()); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| } |
| |
| void MediaStreamManager::StopMediaStreamFromBrowser(const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| const DeviceRequests::const_iterator request_it = FindRequestIterator(label); |
| if (request_it == requests_.end()) { |
| return; |
| } |
| DeviceRequest* const request = request_it->second.get(); |
| |
| SendLogMessage(base::StringPrintf("StopMediaStreamFromBrowser({label=%s})", |
| label.c_str())); |
| |
| // Notify renderers that the devices in the stream will be stopped. |
| if (request->device_stopped_callback) { |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| request->device_stopped_callback.Run(label, device); |
| } |
| } |
| } |
| |
| CancelRequest(request_it); |
| IncrementDesktopCaptureCounter(DESKTOP_CAPTURE_NOTIFICATION_STOP); |
| } |
| |
| void MediaStreamManager::ChangeMediaStreamSourceFromBrowser( |
| const std::string& label, |
| const DesktopMediaID& media_id, |
| bool captured_surface_control_active) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequest* request = FindRequest(label); |
| if (!request) { |
| return; |
| } |
| |
| DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size()); |
| const blink::mojom::StreamDevices& devices = |
| *request->stream_devices_set.stream_devices[0]; |
| |
| if (captured_surface_control_active) { |
| request->SetCapturedSurfaceControlActive(); |
| } |
| |
| if (request->ui_proxy) { |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : |
| {&devices.audio_device, &devices.video_device}) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| const DesktopMediaID old_media_id = DesktopMediaID::Parse(device.id); |
| if (!old_media_id.is_null()) { |
| request->ui_proxy->OnDeviceStoppedForSourceChange( |
| label, old_media_id, media_id, |
| request->captured_surface_control_active()); |
| } |
| } |
| } |
| |
| SendLogMessage(base::StringPrintf( |
| "ChangeMediaStreamSourceFromBrowser({label=%s})", label.c_str())); |
| |
| SetUpDesktopCaptureChangeSourceRequest(request, label, media_id); |
| IncrementDesktopCaptureCounter(DESKTOP_CAPTURE_NOTIFICATION_CHANGE_SOURCE); |
| } |
| |
| void MediaStreamManager::OnRequestStateChangeFromBrowser( |
| const std::string& label, |
| const DesktopMediaID& media_id, |
| blink::mojom::MediaStreamStateChange new_state) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequest* request = FindRequest(label); |
| if (!request) { |
| return; |
| } |
| |
| SendLogMessage(base::StringPrintf("RequestStateChangeFromBrowser({label=%s})", |
| label.c_str())); |
| |
| request->OnRequestStateChangeFromBrowser(label, media_id, new_state); |
| } |
| |
| void MediaStreamManager::WillDestroyCurrentMessageLoop() { |
| DVLOG(3) << "MediaStreamManager::WillDestroyCurrentMessageLoop()"; |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO) || |
| !BrowserThread::IsThreadInitialized(BrowserThread::IO)); |
| if (media_devices_manager_) { |
| media_devices_manager_->StopMonitoring(); |
| } |
| if (video_capture_manager_) { |
| video_capture_manager_->UnregisterListener(this); |
| } |
| if (audio_input_device_manager_) { |
| audio_input_device_manager_->UnregisterListener(this); |
| } |
| |
| audio_input_device_manager_ = nullptr; |
| video_capture_manager_ = nullptr; |
| media_devices_manager_ = nullptr; |
| media_stream_manager = nullptr; |
| preferred_audio_output_device_manager_ = nullptr; |
| requests_.clear(); |
| dispatcher_hosts_.Clear(); |
| video_capture_hosts_.Clear(); |
| } |
| |
| void MediaStreamManager::NotifyDevicesChanged( |
| MediaDeviceType device_type, |
| const blink::WebMediaDeviceInfoArray& devices) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf("NotifyDevicesChanged({device_type=%s})", |
| DeviceTypeToString(device_type))); |
| |
| MediaObserver* media_observer = |
| GetContentClient()->browser()->GetMediaObserver(); |
| |
| MediaStreamType stream_type = ConvertToMediaStreamType(device_type); |
| MediaStreamDevices new_devices = |
| ConvertToMediaStreamDevices(stream_type, devices); |
| |
| if (blink::IsAudioInputMediaType(stream_type)) { |
| MediaCaptureDevicesImpl::GetInstance()->OnAudioCaptureDevicesChanged( |
| new_devices); |
| if (media_observer) { |
| media_observer->OnAudioCaptureDevicesChanged(); |
| } |
| } else if (blink::IsVideoInputMediaType(stream_type)) { |
| MediaCaptureDevicesImpl::GetInstance()->OnVideoCaptureDevicesChanged( |
| new_devices); |
| if (media_observer) { |
| media_observer->OnVideoCaptureDevicesChanged(); |
| } |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| bool MediaStreamManager::RequestDone(const DeviceRequest& request) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| SendLogMessage(base::StringPrintf( |
| "RequestDone({requester_id=%d}, {request_type=%s})", request.requester_id, |
| RequestTypeToString(request.request_type()))); |
| |
| const bool requested_audio = |
| blink::IsAudioInputMediaType(request.audio_type()); |
| const bool requested_video = |
| blink::IsVideoInputMediaType(request.video_type()); |
| |
| const bool audio_done = |
| !requested_audio || |
| request.state(request.audio_type()) == MEDIA_REQUEST_STATE_DONE || |
| request.state(request.audio_type()) == MEDIA_REQUEST_STATE_ERROR; |
| if (!audio_done) { |
| return false; |
| } |
| |
| const bool video_done = |
| !requested_video || |
| request.state(request.video_type()) == MEDIA_REQUEST_STATE_DONE || |
| request.state(request.video_type()) == MEDIA_REQUEST_STATE_ERROR; |
| if (!video_done) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| MediaStreamProvider* MediaStreamManager::GetDeviceManager( |
| MediaStreamType stream_type) const { |
| if (blink::IsVideoInputMediaType(stream_type)) { |
| return video_capture_manager(); |
| } |
| CHECK(blink::IsAudioInputMediaType(stream_type)); |
| return audio_input_device_manager(); |
| } |
| |
| void MediaStreamManager::OnMediaStreamUIWindowId( |
| MediaStreamType video_type, |
| blink::mojom::StreamDevicesSetPtr stream_devices_set, |
| gfx::NativeViewId window_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!window_id) { |
| return; |
| } |
| |
| if (!blink::IsVideoDesktopCaptureMediaType(video_type)) { |
| return; |
| } |
| |
| // Pass along for desktop screen and window capturing when |
| // DesktopCaptureDevice is used. |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| stream_devices_set->stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (!blink::IsVideoDesktopCaptureMediaType(device.type)) { |
| continue; |
| } |
| |
| DesktopMediaID media_id = DesktopMediaID::Parse(device.id); |
| // WebContentsVideoCaptureDevice is used for tab/webcontents. |
| if (media_id.type == DesktopMediaID::TYPE_WEB_CONTENTS) { |
| continue; |
| } |
| #if defined(USE_AURA) |
| // DesktopCaptureDeviceAura is used when aura_id is valid. |
| if (media_id.window_id > DesktopMediaID::kNullId) { |
| continue; |
| } |
| #endif |
| video_capture_manager_->SetDesktopCaptureWindowId(device.session_id(), |
| window_id); |
| break; |
| } |
| } |
| } |
| |
| void MediaStreamManager::DoNativeLogCallbackRegistration( |
| int renderer_host_id, |
| base::RepeatingCallback<void(const std::string&)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Re-registering (overwriting) is allowed and happens in some tests. |
| log_callbacks_[renderer_host_id] = std::move(callback); |
| } |
| |
| void MediaStreamManager::DoNativeLogCallbackUnregistration( |
| int renderer_host_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| log_callbacks_.erase(renderer_host_id); |
| } |
| |
| // static |
| bool MediaStreamManager::IsOriginAllowed(int render_process_id, |
| const url::Origin& origin) { |
| if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL( |
| render_process_id, origin.GetURL())) { |
| LOG(ERROR) << "MSM: Renderer requested a URL it's not allowed to use: " |
| << origin.Serialize(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // static. |
| PreferredAudioOutputDeviceManager* |
| MediaStreamManager::GetPreferredOutputManagerInstance() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!MediaStreamManager::GetInstance()) { |
| return nullptr; |
| } |
| |
| return MediaStreamManager::GetInstance() |
| ->preferred_audio_output_device_manager(); |
| } |
| |
| void MediaStreamManager::SetCapturingLinkSecured( |
| int render_process_id, |
| const base::UnguessableToken& session_id, |
| MediaStreamType type, |
| bool is_secure) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| for (LabeledDeviceRequest& labeled_request : requests_) { |
| DeviceRequest* request = labeled_request.second.get(); |
| if (request->requesting_render_frame_host_id.child_id != |
| render_process_id) { |
| continue; |
| } |
| |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (device.session_id() == session_id && device.type == type) { |
| request->SetCapturingLinkSecured(is_secure); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| void MediaStreamManager::SetStateForTesting( |
| size_t request_index, |
| blink::mojom::MediaStreamType stream_type, |
| MediaRequestState new_state) { |
| DCHECK_LT(request_index, requests_.size()); |
| auto requests_iterator = requests_.begin(); |
| std::advance(requests_iterator, request_index); |
| requests_iterator->second->SetState(stream_type, new_state); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| void MediaStreamManager::SetConditionalFocusWindowForTesting( |
| base::TimeDelta window) { |
| conditional_focus_window_ = window; |
| } |
| |
| void MediaStreamManager::SetCapturedSurfaceControllerFactoryForTesting( |
| CapturedSurfaceControllerFactoryCallback factory) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| captured_surface_controller_factory_ = std::move(factory); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| void MediaStreamManager::SetGenerateStreamsCallbackForTesting( |
| GenerateStreamTestCallback test_callback) { |
| generate_stream_test_callback_ = std::move(test_callback); |
| } |
| |
| void MediaStreamManager::SetPreferredAudioOutputDeviceManagerForTesting( |
| std::unique_ptr<PreferredAudioOutputDeviceManager> manager) { |
| preferred_audio_output_device_manager_ = std::move(manager); |
| } |
| |
| MediaStreamDevices MediaStreamManager::ConvertToMediaStreamDevices( |
| MediaStreamType stream_type, |
| const blink::WebMediaDeviceInfoArray& device_infos) { |
| MediaStreamDevices devices; |
| for (const auto& info : device_infos) { |
| devices.emplace_back( |
| stream_type, info.device_id, info.label, info.video_control_support, |
| static_cast<media::VideoFacingMode>(info.video_facing), info.group_id); |
| } |
| |
| return devices; |
| } |
| |
| void MediaStreamManager::ActivateTabOnUIThread(const DesktopMediaID source) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| RenderFrameHostImpl* rfh = |
| RenderFrameHostImpl::FromID(source.web_contents_id.render_process_id, |
| source.web_contents_id.main_render_frame_id); |
| if (rfh) { |
| rfh->render_view_host()->GetDelegate()->Activate(); |
| } |
| } |
| |
| void MediaStreamManager::OnStreamStarted(const std::string& label) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DeviceRequest* const request = FindRequest(label); |
| if (!request) { |
| return; |
| } |
| SendLogMessage(base::StringPrintf( |
| "OnStreamStarted({label=%s}, {requester_id=%d}, {request_type=%s})", |
| label.c_str(), request->requester_id, |
| RequestTypeToString(request->request_type()))); |
| |
| MediaStreamUI::SourceCallback device_changed_callback; |
| if (request->stream_controls().dynamic_surface_switching_requested && |
| ChangeSourceSupported( |
| blink::ToMediaStreamDevicesList(request->stream_devices_set))) { |
| device_changed_callback = base::BindRepeating( |
| &MediaStreamManager::ChangeMediaStreamSourceFromBrowser, |
| base::Unretained(this), label); |
| } |
| |
| std::vector<DesktopMediaID> screen_share_ids; |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (blink::IsVideoScreenCaptureMediaType(device.type)) { |
| screen_share_ids.push_back(DesktopMediaID::Parse(device.id)); |
| } |
| } |
| } |
| |
| // base::Unretained is safe here because MediaStreamManager is deleted on the |
| // UI thread, after the IO thread has been stopped. |
| if (request->ui_proxy) { |
| request->ui_proxy->OnStarted( |
| base::BindOnce(&MediaStreamManager::StopMediaStreamFromBrowser, |
| base::Unretained(this), label), |
| device_changed_callback, |
| base::BindOnce(&MediaStreamManager::OnMediaStreamUIWindowId, |
| base::Unretained(this), request->video_type(), |
| request->stream_devices_set.Clone()), |
| label, screen_share_ids, |
| base::BindRepeating( |
| &MediaStreamManager::OnRequestStateChangeFromBrowser, |
| base::Unretained(this), label)); |
| } |
| } |
| |
| void MediaStreamManager::OnCaptureConfigurationChanged( |
| const base::UnguessableToken& session_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (LabeledDeviceRequest& labeled_request : requests_) { |
| const std::string& label = labeled_request.first; |
| DeviceRequest* request = labeled_request.second.get(); |
| for (const auto& stream_devices_ptr : |
| request->stream_devices_set.stream_devices) { |
| const blink::MediaStreamDevice* const device_ptr = |
| GetStreamDevice(*stream_devices_ptr, session_id); |
| if (!device_ptr) { |
| continue; |
| } |
| request->OnCaptureConfigurationChanged(label, *device_ptr); |
| } |
| } |
| } |
| |
| void MediaStreamManager::OnRegionCaptureRectChanged( |
| const base::UnguessableToken& session_id, |
| const std::optional<gfx::Rect>& region_capture_rect) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| for (const LabeledDeviceRequest& labeled_device_request : requests_) { |
| DeviceRequest* const device_request = labeled_device_request.second.get(); |
| if (!device_request || !device_request->ui_proxy) { |
| continue; |
| } |
| |
| for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr : |
| labeled_device_request.second->stream_devices_set.stream_devices) { |
| const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr; |
| |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : { |
| &stream_devices.audio_device, |
| &stream_devices.video_device, |
| }) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (blink::IsVideoInputMediaType(device.type) && |
| session_id == device.session_id()) { |
| // Note: |device_request->ui_proxy != nullptr| tested in external |
| // loop. |
| device_request->ui_proxy->OnRegionCaptureRectChanged( |
| region_capture_rect); |
| } |
| } |
| } |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_SCREEN_CAPTURE) |
| void MediaStreamManager::SetCapturedDisplaySurfaceFocus( |
| const std::string& label, |
| bool focus, |
| bool is_from_microtask, |
| bool is_from_timer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequest* const request = FindRequest(label); |
| if (!request) { |
| return; |
| } |
| |
| if (!request->ui_proxy) { |
| return; |
| } |
| |
| DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size()); |
| const blink::mojom::StreamDevices& devices = |
| *request->stream_devices_set.stream_devices[0]; |
| |
| DesktopMediaID media_id; |
| for (const std::optional<blink::MediaStreamDevice>* device_ptr : |
| {&devices.audio_device, &devices.video_device}) { |
| if (!device_ptr->has_value()) { |
| continue; |
| } |
| const blink::MediaStreamDevice& device = device_ptr->value(); |
| if (blink::IsVideoInputMediaType(device.type)) { |
| media_id = DesktopMediaID::Parse(device.id); |
| break; |
| } |
| } |
| |
| if (media_id.is_null()) { |
| return; |
| } |
| |
| if (media_id.type != DesktopMediaID::Type::TYPE_WEB_CONTENTS && |
| media_id.type != DesktopMediaID::Type::TYPE_WINDOW) { |
| return; // Video device not focus-able. |
| } |
| |
| request->ui_proxy->SetFocus(media_id, focus, is_from_microtask, |
| is_from_timer); |
| } |
| #endif // BUILDFLAG(ENABLE_SCREEN_CAPTURE) |
| |
| #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| void MediaStreamManager::SendWheel( |
| GlobalRenderFrameHostId capturer_rfh_id, |
| const base::UnguessableToken& session_id, |
| blink::mojom::CapturedWheelActionPtr action, |
| base::OnceCallback<void(CapturedSurfaceControlResult)> callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| CapturedSurfaceControlResult result; |
| CapturedSurfaceController* const controller = |
| GetCapturedSurfaceController(capturer_rfh_id, session_id, result); |
| if (!controller) { |
| std::move(callback).Run(result); |
| return; |
| } |
| |
| controller->SendWheel(std::move(action), std::move(callback)); |
| } |
| |
| void MediaStreamManager::UpdateZoomLevel( |
| GlobalRenderFrameHostId capturer_rfh_id, |
| const base::UnguessableToken& session_id, |
| blink::mojom::ZoomLevelAction action, |
| base::OnceCallback<void(blink::mojom::CapturedSurfaceControlResult)> |
| callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequest* const request = FindRequestByVideoSessionId(session_id); |
| if (!request) { |
| std::move(callback).Run(blink::mojom::CapturedSurfaceControlResult:: |
| kCapturedSurfaceNotFoundError); |
| return; |
| } |
| if (request->requesting_render_frame_host_id != capturer_rfh_id) { |
| std::move(callback).Run( |
| blink::mojom::CapturedSurfaceControlResult::kUnknownError); |
| return; |
| } |
| |
| CapturedSurfaceController* const controller = |
| request->captured_surface_controller(); |
| if (!controller) { |
| std::move(callback).Run( |
| blink::mojom::CapturedSurfaceControlResult::kUnknownError); |
| return; |
| } |
| |
| controller->UpdateZoomLevel(action, std::move(callback)); |
| } |
| |
| void MediaStreamManager::RequestCapturedSurfaceControlPermission( |
| GlobalRenderFrameHostId capturer_rfh_id, |
| const base::UnguessableToken& session_id, |
| base::OnceCallback<void(blink::mojom::CapturedSurfaceControlResult)> |
| callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| CapturedSurfaceControlResult result; |
| CapturedSurfaceController* const controller = |
| GetCapturedSurfaceController(capturer_rfh_id, session_id, result); |
| if (!controller) { |
| std::move(callback).Run(result); |
| return; |
| } |
| |
| controller->RequestPermission(std::move(callback)); |
| } |
| |
| #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) |
| |
| void MediaStreamManager::RegisterDispatcherHost( |
| std::unique_ptr<blink::mojom::MediaStreamDispatcherHost> host, |
| mojo::PendingReceiver<blink::mojom::MediaStreamDispatcherHost> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| dispatcher_hosts_.Add(std::move(host), std::move(receiver)); |
| } |
| |
| void MediaStreamManager::RegisterVideoCaptureHost( |
| std::unique_ptr<media::mojom::VideoCaptureHost> host, |
| mojo::PendingReceiver<media::mojom::VideoCaptureHost> receiver) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| video_capture_hosts_.Add(std::move(host), std::move(receiver)); |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |
| if (base::FeatureList::IsEnabled(kReleaseVideoSourceProviderIfNotInUse)) { |
| media_devices_manager_->UpdateVideoCaptureHostsEmptyState( |
| video_capture_hosts_.empty()); |
| } |
| #endif |
| } |
| |
| std::optional<url::Origin> MediaStreamManager::GetOriginByVideoSessionId( |
| const base::UnguessableToken& session_id) { |
| DeviceRequest* request = FindRequestByVideoSessionId(session_id); |
| if (request == nullptr) { |
| return std::nullopt; |
| } |
| return request->salt_and_origin.origin(); |
| } |
| |
| // static |
| PermissionControllerImpl* MediaStreamManager::GetPermissionController( |
| GlobalRenderFrameHostId requesting_render_frame_host_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| RenderFrameHost* rfh = |
| RenderFrameHost::FromID(requesting_render_frame_host_id); |
| if (!rfh) { |
| return nullptr; |
| } |
| |
| return PermissionControllerImpl::FromBrowserContext(rfh->GetBrowserContext()); |
| } |
| |
| void MediaStreamManager::SubscribeToPermissionController( |
| const std::string& label, |
| const DeviceRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request); |
| |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &MediaStreamManager::SubscribeToPermissionControllerOnUIThread, |
| base::Unretained(this), label, |
| request->requesting_render_frame_host_id, request->requester_id, |
| request->page_request_id, |
| blink::IsAudioInputMediaType(request->audio_type()), |
| blink::IsVideoInputMediaType(request->video_type()), |
| request->salt_and_origin.origin().GetURL())); |
| } |
| |
| void MediaStreamManager::SubscribeToPermissionControllerOnUIThread( |
| const std::string& label, |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| bool is_audio_request, |
| bool is_video_request, |
| const GURL& origin) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| PermissionControllerImpl* controller = |
| GetPermissionController(requesting_render_frame_host_id); |
| if (!controller) { |
| return; |
| } |
| |
| PermissionController::SubscriptionId audio_subscription_id; |
| PermissionController::SubscriptionId video_subscription_id; |
| |
| if (is_audio_request) { |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| audio_subscription_id = controller->SubscribeToPermissionStatusChange( |
| blink::PermissionType::AUDIO_CAPTURE, |
| /*render_process_host=*/nullptr, |
| RenderFrameHost::FromID(requesting_render_frame_host_id), origin, |
| /*should_include_device_status=*/false, |
| base::BindRepeating(&MediaStreamManager::PermissionChangedCallback, |
| base::Unretained(this), |
| requesting_render_frame_host_id, requester_id, |
| page_request_id)); |
| } |
| |
| if (is_video_request) { |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| video_subscription_id = controller->SubscribeToPermissionStatusChange( |
| blink::PermissionType::VIDEO_CAPTURE, |
| /*render_process_host=*/nullptr, |
| RenderFrameHost::FromID(requesting_render_frame_host_id), origin, |
| /*should_include_device_status=*/false, |
| base::BindRepeating(&MediaStreamManager::PermissionChangedCallback, |
| base::Unretained(this), |
| requesting_render_frame_host_id, requester_id, |
| page_request_id)); |
| } |
| |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::SetPermissionSubscriptionIDs, |
| base::Unretained(this), label, |
| requesting_render_frame_host_id, audio_subscription_id, |
| video_subscription_id)); |
| } |
| |
| void MediaStreamManager::SetPermissionSubscriptionIDs( |
| const std::string& label, |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| PermissionController::SubscriptionId audio_subscription_id, |
| PermissionController::SubscriptionId video_subscription_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| DeviceRequest* const request = FindRequest(label); |
| if (!request) { |
| // Something happened with the request while the permission subscription was |
| // created, unsubscribe to clean up. |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &MediaStreamManager::UnsubscribeFromPermissionControllerOnUIThread, |
| requesting_render_frame_host_id, audio_subscription_id, |
| video_subscription_id)); |
| |
| return; |
| } |
| |
| request->audio_subscription_id = audio_subscription_id; |
| request->video_subscription_id = video_subscription_id; |
| } |
| |
| // static |
| void MediaStreamManager::UnsubscribeFromPermissionControllerOnUIThread( |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| PermissionController::SubscriptionId audio_subscription_id, |
| PermissionController::SubscriptionId video_subscription_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| PermissionControllerImpl* controller = |
| GetPermissionController(requesting_render_frame_host_id); |
| if (!controller) { |
| return; |
| } |
| |
| controller->UnsubscribeFromPermissionStatusChange(audio_subscription_id); |
| controller->UnsubscribeFromPermissionStatusChange(video_subscription_id); |
| } |
| |
| void MediaStreamManager::PermissionChangedCallback( |
| GlobalRenderFrameHostId requesting_render_frame_host_id, |
| int requester_id, |
| int page_request_id, |
| blink::mojom::PermissionStatus status) { |
| if (status == blink::mojom::PermissionStatus::GRANTED) { |
| return; |
| } |
| |
| if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MediaStreamManager::PermissionChangedCallback, |
| base::Unretained(this), requesting_render_frame_host_id, |
| requester_id, page_request_id, status)); |
| |
| return; |
| } |
| |
| CancelRequest(requesting_render_frame_host_id, requester_id, page_request_id); |
| } |
| |
| void MediaStreamManager::MaybeStartTrackingCaptureHandleConfig( |
| const std::string& label, |
| const MediaStreamDevice& captured_device, |
| DeviceRequest& request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!blink::IsVideoInputMediaType(captured_device.type) || |
| !WebContentsMediaCaptureId::Parse(captured_device.id, nullptr)) { |
| return; |
| } |
| |
| // It is safe to bind base::Unretained(this) because MediaStreamManager is |
| // owned by BrowserMainLoop. |
| // Since |capture_handle_manager_| is owned by |this|, it is also safe to |
| // bind base::Unretained(&capture_handle_manager_). |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CaptureHandleManager::OnTabCaptureStarted, |
| base::Unretained(&capture_handle_manager_), label, |
| captured_device, request.requesting_render_frame_host_id, |
| base::BindPostTask(GetIOThreadTaskRunner({}), |
| request.OnCaptureHandleChangeCb()))); |
| } |
| |
| void MediaStreamManager::MaybeStopTrackingCaptureHandleConfig( |
| const std::string& label, |
| const MediaStreamDevice& captured_device) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (!blink::IsVideoInputMediaType(captured_device.type) || |
| !WebContentsMediaCaptureId::Parse(captured_device.id, nullptr)) { |
| return; |
| } |
| |
| // It is safe to bind base::Unretained(&capture_handle_manager_) because |
| // it is owned by MediaStreamManager, which is in turn owned by |
| // BrowserMainLoop. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&CaptureHandleManager::OnTabCaptureStopped, |
| base::Unretained(&capture_handle_manager_), |
| label, captured_device)); |
| } |
| |
| void MediaStreamManager::MaybeUpdateTrackedCaptureHandleConfigs( |
| const std::string& label, |
| const blink::mojom::StreamDevicesSet& new_devices_set, |
| DeviceRequest& request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK_EQ(1u, new_devices_set.stream_devices.size()); |
| |
| const blink::mojom::StreamDevices& new_devices = |
| *new_devices_set.stream_devices[0]; |
| blink::mojom::StreamDevicesSetPtr filtered_new_devices_set = |
| blink::mojom::StreamDevicesSet::New(); |
| filtered_new_devices_set->stream_devices.emplace_back( |
| blink::mojom::StreamDevices::New()); |
| blink::mojom::StreamDevices& filtered_new_devices = |
| *filtered_new_devices_set->stream_devices[0]; |
| if (new_devices.video_device.has_value() && |
| WebContentsMediaCaptureId::Parse(new_devices.video_device->id, nullptr)) { |
| filtered_new_devices.video_device = new_devices.video_device.value(); |
| } |
| |
| // It is safe to bind base::Unretained(&capture_handle_manager_) because |
| // it is owned by MediaStreamManager, which is in turn owned by |
| // BrowserMainLoop. |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CaptureHandleManager::OnTabCaptureDevicesUpdated, |
| base::Unretained(&capture_handle_manager_), label, |
| std::move(filtered_new_devices_set), |
| request.requesting_render_frame_host_id, |
| base::BindPostTask(GetIOThreadTaskRunner({}), |
| request.OnCaptureHandleChangeCb()))); |
| } |
| |
| bool MediaStreamManager::ShouldUseFakeUIProxy( |
| const DeviceRequest& request) const { |
| if (!fake_ui_factory_) { |
| return false; |
| } |
| |
| if (use_fake_ui_only_for_camera_and_microphone_) { |
| return request.audio_type() == MediaStreamType::DEVICE_AUDIO_CAPTURE || |
| request.video_type() == MediaStreamType::DEVICE_VIDEO_CAPTURE; |
| } |
| |
| return request.video_type() != MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE || |
| use_fake_ui_for_gum_desktop_capture_; |
| } |
| |
| std::unique_ptr<MediaStreamUIProxy> MediaStreamManager::MakeFakeUIProxy( |
| const std::string& label, |
| const MediaDeviceEnumeration& enumeration, |
| DeviceRequest* request) { |
| // Just auto-select from the available devices. |
| MediaStreamDevices devices; |
| if (request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE || |
| request->video_type() == |
| MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB || |
| request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET) { |
| devices = DisplayMediaDevicesFromFakeDeviceConfig( |
| request->video_type(), |
| request->audio_type() == MediaStreamType::DISPLAY_AUDIO_CAPTURE, |
| request->requesting_render_frame_host_id, |
| request->stream_controls().preferred_display_surface, |
| request->stream_controls().exclude_monitor_type_surfaces, |
| fake_ui_factory_captured_tab_id_); |
| } else if (request->video_type() == |
| MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) { |
| // Cache the |label| in the device name field, for unit test purpose only. |
| devices.emplace_back( |
| MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, |
| DesktopMediaID(DesktopMediaID::TYPE_SCREEN, DesktopMediaID::kNullId) |
| .ToString(), |
| label); |
| } else { |
| MediaStreamDevices audio_devices = ConvertToMediaStreamDevices( |
| request->audio_type(), |
| enumeration[static_cast<size_t>(MediaDeviceType::kMediaAudioInput)]); |
| MediaStreamDevices video_devices = ConvertToMediaStreamDevices( |
| request->video_type(), |
| enumeration[static_cast<size_t>(MediaDeviceType::kMediaVideoInput)]); |
| devices.reserve(audio_devices.size() + video_devices.size()); |
| devices.insert(devices.end(), audio_devices.begin(), audio_devices.end()); |
| devices.insert(devices.end(), video_devices.begin(), video_devices.end()); |
| } |
| |
| std::unique_ptr<FakeMediaStreamUIProxy> fake_ui = fake_ui_factory_.Run(); |
| fake_ui->AddAvailableDevices(devices); |
| |
| return fake_ui; |
| } |
| |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) |
| void MediaStreamManager::OnVideoCaptureHostConnectionError() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| media_devices_manager_->UpdateVideoCaptureHostsEmptyState( |
| video_capture_hosts_.empty()); |
| } |
| #endif |
| |
| } // namespace content |