// 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/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.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/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/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/browser/security/cpsp/child_process_security_policy_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/permission_descriptor_util.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 "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;

// If enabled, the device name is also used when comparing devices in
// FindExistingRequestedDevice.
BASE_FEATURE(kEnumerateDevicesUseNameInDeviceComparison,
             base::FEATURE_ENABLED_BY_DEFAULT);

namespace {
enum class StopAudioEvent {
  kStopAudioCallbackCalled = 0,
  kApplicationAudioStopped = 1,
  kMaxValue = kApplicationAudioStopped
};

// Turns off available audio effects (removes the flag) if the options
// explicitly turn them off.
void FilterAudioEffects(const StreamControls& controls, int* effects) {
  CHECK(effects, base::NotFatalUntil::M158);
  // TODO(ajm): Should we handle ECHO_CANCELLER here?
}

// 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();
  }
}

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.value(),
      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.value(),
      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.value(),
      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;
          // TODO(crbug.com/379869738) Remove GetUnsafeValue.
          web_contents_id = captured_tab_id.value_or(WebContentsMediaCaptureId{
              render_frame_host_id.child_id.GetUnsafeValue(),
              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;
      // TODO(crbug.com/379869738) Remove GetUnsafeValue.
      web_contents_id = captured_tab_id.value_or(WebContentsMediaCaptureId{
          render_frame_host_id.child_id.GetUnsafeValue(),
          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 (!std::ranges::contains(devices, MediaStreamType::DISPLAY_VIDEO_CAPTURE,
                             &MediaStreamDevice::type) &&
      !std::ranges::contains(devices,
                             MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB,
                             &MediaStreamDevice::type)) {
    return false;  // Not an API call that supports share-this-tab-instead.
  }

  return true;  // getDisplayMedia() now supported by default.
}

#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);
}

bool IsWindowCaptureId(const std::string& device_id) {
  content::DesktopMediaID desktop_id =
      content::DesktopMediaID::Parse(device_id);
  return !desktop_id.is_null() &&
         desktop_id.type == content::DesktopMediaID::TYPE_WINDOW;
}

// Capture handle can only be exposed for individual tabs, IWAs,
// and standalone PWAs.
bool IsEligibleForCaptureHandle(const std::string& device_id) {
  return WebContentsMediaCaptureId::Parse(device_id, nullptr) ||
         IsWindowCaptureId(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) {
    // Note: The label will be an empty string at this point. It is set in
    // AddRequest().
    SendLogMessage(base::StringPrintf(
        "DR::DeviceRequest({label=%s}, {requesting_process_id=%d}, "
        "{requesting_frame_id=%d}, {requester_id=%d}, {request_type=%s})",
        label_.c_str(), requesting_render_frame_host_id.child_id.value(),
        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 std::string& label() const { return label_; }

  const StreamControls& stream_controls() const { return stream_controls_; }

  void SetAudioType(MediaStreamType audio_type) {
    CHECK(blink::IsAudioInputMediaType(audio_type) ||
              audio_type == MediaStreamType::NO_SERVICE,
          base::NotFatalUntil::M158);
    SendLogMessage(base::StringPrintf(
        "DR::SetAudioType({label=%s}, [requester_id=%d], {audio_type=%s})",
        label_.c_str(), requester_id, StreamTypeToString(audio_type)));
    audio_type_ = audio_type;
  }

  MediaStreamType audio_type() const { return audio_type_; }

  void SetVideoType(MediaStreamType video_type) {
    CHECK(blink::IsVideoInputMediaType(video_type) ||
              video_type == MediaStreamType::NO_SERVICE,
          base::NotFatalUntil::M158);
    SendLogMessage(base::StringPrintf(
        "DR::SetVideoType({label=%s}, [requester_id=%d], {video_type=%s})",
        label_.c_str(), requester_id, StreamTypeToString(video_type)));
    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) {
    CHECK(!ui_request_, base::NotFatalUntil::M158);
    SendLogMessage(base::StringPrintf(
        "DR::CreateUIRequest({label=%s}, [requester_id=%d], "
        "{requested_audio_device_id=%s}, {requested_video_device_id=%s})",
        label_.c_str(), 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;
    // TODO(crbug.com/379869738) Remove GetUnsafeValue.
    ui_request_ = std::make_unique<MediaStreamRequest>(
        requesting_render_frame_host_id.child_id.GetUnsafeValue(),
        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;
    ui_request_->audio_selection_preferred =
        stream_controls_.audio_selection_preferred;
  }

  // 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) {
    CHECK(!ui_request_, base::NotFatalUntil::M158);
    target_render_frame_host_id_ = target_render_frame_host_id;
    // TODO(crbug.com/379869738) Remove GetUnsafeValue.
    ui_request_ = std::make_unique<MediaStreamRequest>(
        target_render_frame_host_id_.child_id.GetUnsafeValue(),
        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;
    ui_request_->audio_selection_preferred =
        stream_controls_.audio_selection_preferred;
  }

  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({label=%s}, [requester_id=%d], {stream_type=%s}, "
        "{new_state=%s})",
        label_.c_str(), 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) {
        // TODO(crbug.com/379869738) Remove GetUnsafeValue.
        media_observer->OnMediaRequestStateChanged(
            target_render_frame_host_id_.child_id.GetUnsafeValue(),
            target_render_frame_host_id_.frame_routing_id, page_request_id,
            salt_and_origin.origin().GetURL(), static_cast<MediaStreamType>(i),
            new_state);
      }
    } else {
      // TODO(crbug.com/379869738) Remove GetUnsafeValue.
      media_observer->OnMediaRequestStateChanged(
          target_render_frame_host_id_.child_id.GetUnsafeValue(),
          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;
    }

    // TODO(crbug.com/379869738) Remove GetUnsafeValue.
    media_observer->OnSetCapturingLinkSecured(
        target_render_frame_host_id_.child_id.GetUnsafeValue(),
        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_.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() {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    return captured_surface_controller_.get();
  }

  void SetCapturedSurfaceController(
      std::unique_ptr<CapturedSurfaceController> controller) {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    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;

  bool is_allowed_while_screen_locked = false;

  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: {
        base::OnceClosure stop_callback = base::BindPostTask(
            GetIOThreadTaskRunner({}),
            base::BindOnce(&MediaStreamManager::StopMediaStreamFromBrowser,
                           base::Unretained(MediaStreamManager::GetInstance()),
                           label_));
        GetUIThreadTaskRunner({})->PostTask(
            FROM_HERE,
            base::BindOnce(
                [](GlobalRenderFrameHostId renderer_id, std::string label,
                   base::OnceClosure stop_callback) {
                  GetContentClient()->browser()->NotifyMultiCaptureStateChanged(
                      renderer_id, label,
                      ContentBrowserClient::MultiCaptureChanged::kStarted,
                      std::move(stop_callback));
                },
                frame_host_id, label_, std::move(stop_callback)));
        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,
                      base::NullCallback());
                },
                frame_host_id, label_));
        break;
      case MediaRequestState::MEDIA_REQUEST_STATE_CLOSING:
        GetUIThreadTaskRunner({})->PostTask(
            FROM_HERE,
            base::BindOnce(
                [](GlobalRenderFrameHostId renderer_id, std::string label) {
                  GetContentClient()->browser()->NotifyMultiCaptureStateChanged(
                      renderer_id, label,
                      ContentBrowserClient::MultiCaptureChanged::kStopped,
                      base::NullCallback());
                },
                frame_host_id, label_));
        break;
      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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(media_access_request_callback_, base::NotFatalUntil::M158);
    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(media_access_request_callback_, base::NotFatalUntil::M158);
    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(device_changed_callback_, base::NotFatalUntil::M158);
    CHECK_EQ(1u, old_stream_devices_set.stream_devices.size(),
             base::NotFatalUntil::M158);
    CHECK_EQ(1u, stream_devices_set.stream_devices.size(),
             base::NotFatalUntil::M158);

    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK_EQ(1u, stream_devices_set.stream_devices.size(),
             base::NotFatalUntil::M158);
    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

    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)) {
    CHECK(generate_streams_callback_, base::NotFatalUntil::M158);
  }

  ~GenerateStreamsRequest() override {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    if (generate_streams_callback_) {
      std::move(generate_streams_callback_)
          .Run(MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN_REQUEST_REMOVED,
               /*label=*/std::string(),
               /*stream_devices_set=*/nullptr,
               /*pan_tilt_zoom_allowed=*/false);
    }
  }

  void PanTiltZoomPermissionChecked(const std::string& label,
                                    bool pan_tilt_zoom_allowed) override {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(generate_streams_callback_, base::NotFatalUntil::M158);

    MaybeConferTransientActivation();

    std::move(generate_streams_callback_)
        .Run(MediaStreamRequestResult::OK, label, stream_devices_set.Clone(),
             pan_tilt_zoom_allowed);
  }

  void FinalizeRequestFailed(MediaStreamRequestResult result) override {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(generate_streams_callback_, base::NotFatalUntil::M158);
    std::move(generate_streams_callback_)
        .Run(result, /*label=*/std::string(),
             /*stream_devices_set=*/nullptr,
             /*pan_tilt_zoom_allowed=*/false);
  }

 private:
  void MaybeConferTransientActivation() {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

    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)) {
    CHECK(get_open_device_callback_, base::NotFatalUntil::M158);
  }

  ~GetOpenDeviceRequest() override {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    if (get_open_device_callback_) {
      std::move(get_open_device_callback_)
          .Run(MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN_OTHER, nullptr);
    }
  }

  void PanTiltZoomPermissionChecked(const std::string& label,
                                    bool pan_tilt_zoom_allowed) override {
    CHECK(get_open_device_callback_, base::NotFatalUntil::M158);
    // GetOpenDevice is only available with exactly one stream.
    CHECK_EQ(stream_devices_set.stream_devices.size(), 1u,
             base::NotFatalUntil::M158);
    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.
    CHECK_NE(stream_devices.audio_device.has_value(),
             stream_devices.video_device.has_value(),
             base::NotFatalUntil::M158);
    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(get_open_device_callback_, base::NotFatalUntil::M158);
    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)) {
    CHECK(open_device_callback_, base::NotFatalUntil::M158);
  }

  ~OpenDeviceRequest() override {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    if (open_device_callback_) {
      std::move(open_device_callback_)
          .Run(/*success=*/false, std::string(), MediaStreamDevice());
    }
  }

  void FinalizeRequest(const std::string& label) override {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(open_device_callback_, base::NotFatalUntil::M158);
    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 {
    CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
    CHECK(open_device_callback_, base::NotFatalUntil::M158);
    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() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  return media_stream_manager;
}

MediaStreamManager::MediaStreamManager(media::AudioSystem* audio_system)
    : MediaStreamManager(audio_system, nullptr) {}

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);
  }

  CHECK(audio_system_, base::NotFatalUntil::M158);

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

#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() {
  CHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO),
        base::NotFatalUntil::M158);
  CHECK(requests_.empty(), base::NotFatalUntil::M158);
}

VideoCaptureManager* MediaStreamManager::video_capture_manager() const {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(video_capture_manager_.get(), base::NotFatalUntil::M158);
  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.
  CHECK(audio_input_device_manager_.get(), base::NotFatalUntil::M158);
  return audio_input_device_manager_.get();
}

MediaDevicesManager* MediaStreamManager::media_devices_manager() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  // nullptr might be returned during shutdown.
  return media_devices_manager_.get();
}

media::AudioSystem* MediaStreamManager::audio_system() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  return audio_system_;
}

PreferredAudioOutputDeviceManager*
MediaStreamManager::preferred_audio_output_device_manager() {
  return preferred_audio_output_device_manager_.get();
}

void MediaStreamManager::AddVideoCaptureObserver(
    media::VideoCaptureObserver* capture_observer) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  if (video_capture_manager_) {
    video_capture_manager_->AddVideoCaptureObserver(capture_observer);
  }
}

void MediaStreamManager::RemoveAllVideoCaptureObservers() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer),
        base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer),
        base::NotFatalUntil::M158);

  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 {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(type == MediaStreamType::DEVICE_AUDIO_CAPTURE ||
            type == MediaStreamType::DEVICE_VIDEO_CAPTURE,
        base::NotFatalUntil::M158);
  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() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  // 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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(type == MediaDeviceType::kMediaAudioInput ||
            type == MediaDeviceType::kMediaVideoInput,
        base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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_->StartMonitoringAndPopulateCache(0, 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.
  CHECK(request_audio_input || request_video_input, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  // 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 {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  return std::ranges::find(requests_, label, &LabeledDeviceRequest::first);
}

MediaStreamManager::DeviceRequest* MediaStreamManager::FindRequest(
    const std::string& label) const {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  MediaStreamManager::DeviceRequests::const_iterator it =
      FindRequestIterator(label);
  return (it != requests_.end()) ? it->second.get() : nullptr;
}

MediaStreamManager::DeviceRequest* MediaStreamManager::FindRequestBySessionId(
    const base::UnguessableToken& session_id,
    SessionType* out_type) const {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  if (session_id.is_empty()) {
    return nullptr;
  }

  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) {
      if (stream_devices_ptr->audio_device.has_value() &&
          stream_devices_ptr->audio_device->session_id() == session_id) {
        if (out_type) {
          *out_type = SessionType::kAudio;
        }
        return request;
      }
      if (stream_devices_ptr->video_device.has_value() &&
          stream_devices_ptr->video_device->session_id() == session_id) {
        if (out_type) {
          *out_type = SessionType::kVideo;
        }
        return request;
      }
    }
  }

  return nullptr;
}

bool MediaStreamManager::ValidateSession(
    const base::UnguessableToken& session_id,
    const GlobalRenderFrameHostId& render_frame_host_id,
    SessionType expected_type) const {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  SessionType actual_type;
  DeviceRequest* const request =
      FindRequestBySessionId(session_id, &actual_type);
  if (!request) {
    return true;  // Safe: session not active.
  }
  return request->requesting_render_frame_host_id == render_frame_host_id &&
         actual_type == expected_type;
}

bool MediaStreamManager::ValidateAudioSession(
    const base::UnguessableToken& session_id,
    const GlobalRenderFrameHostId& render_frame_host_id) const {
  return ValidateSession(session_id, render_frame_host_id, SessionType::kAudio);
}

bool MediaStreamManager::ValidateVideoSession(
    const base::UnguessableToken& session_id,
    const GlobalRenderFrameHostId& render_frame_host_id) const {
  return ValidateSession(session_id, render_frame_host_id, SessionType::kVideo);
}

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

CapturedSurfaceController* MediaStreamManager::GetCapturedSurfaceController(
    GlobalRenderFrameHostId capturer_rfh_id,
    const base::UnguessableToken& session_id,
    blink::mojom::CapturedSurfaceControlResult& result) {
  SessionType actual_type;
  DeviceRequest* const request =
      FindRequestBySessionId(session_id, &actual_type);
  if (!request || actual_type != SessionType::kVideo) {
    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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  DeviceRequest* const new_request = FindRequest(new_label);
  CHECK(new_request, base::NotFatalUntil::M158);
  // TODO(crbug.com/40846554): Generalize to multiple streams.
  CHECK(new_request->stream_devices_set.stream_devices.empty(),
        base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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,
                  base::NullCallback());
            },
            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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  // 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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(!output_parameters || output_parameters->IsValid(),
        base::NotFatalUntil::M158);
  DeviceRequest* request = FindRequest(label);
  if (!request) {
    return;
  }
  CHECK(request->HasUIRequest(), base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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::INVALID_DISPLAY_CAPTURE_CONSTRAINTS);
    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::INVALID_GUM_TAB_CAPTURE_CONSTRAINTS);
    }
    return;
  }

  const bool is_screen_capture =
      request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
  if (is_screen_capture && !SetUpScreenCaptureRequest(request)) {
    FinalizeRequestFailed(
        request_it,
        MediaStreamRequestResult::INVALID_GUM_SCREEN_CAPTURE_CONSTRAINTS);
    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* manager, const std::string& label,
               const blink::mojom::StreamDevicesSet& stream_devices_set,
               blink::mojom::MediaStreamRequestResult result) {
              manager->HandleAccessRequestResponse(
                  label, media::AudioParameters(), stream_devices_set, result,
                  /*is_allowed_while_screen_locked=*/false);
            },
            base::Unretained(this), label));
    return;
  }

  ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration());
}

bool MediaStreamManager::SetUpDisplayCaptureRequest(DeviceRequest* request) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(
      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,
      base::NotFatalUntil::M158);

  // 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) {
  CHECK((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),
        base::NotFatalUntil::M158);
  SendLogMessage(base::StringPrintf(
      "SetUpDeviceCaptureRequest([requester_id=%d] {label=%s})",
      request->requester_id, request->label().c_str()));
  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) {
  CHECK(request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE ||
            request->video_type() == MediaStreamType::GUM_TAB_VIDEO_CAPTURE,
        base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(content::BrowserThread::UI, base::NotFatalUntil::M158);
  // Resolve DesktopMediaID for the specified device id.
  // TODO(crbug.com/379869738) Remove GetUnsafeValue.
  return DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
      capture_device_id,
      requesting_render_frame_host_id.child_id.GetUnsafeValue(),
      requesting_render_frame_host_id.frame_routing_id,
      url::Origin::Create(origin), kRegistryStreamTypeTab);
}

void MediaStreamManager::FinishTabCaptureRequestSetupWithDeviceId(
    const std::string& label,
    const DesktopMediaID& device_id) {
  CHECK_CURRENTLY_ON(content::BrowserThread::IO, base::NotFatalUntil::M158);

  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::STREAM_NOT_FOUND_IN_REGISTRY);
    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) {
  CHECK(request->audio_type() == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE ||
            request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
        base::NotFatalUntil::M158);

  // 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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(blink::IsDesktopCaptureMediaType(request->video_type()),
        base::NotFatalUntil::M158);
  CHECK(request->request_type() == blink::MEDIA_GENERATE_STREAM ||
            request->request_type() == blink::MEDIA_DEVICE_UPDATE,
        base::NotFatalUntil::M158);

  // 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 {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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 {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);

  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 {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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 {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(existing_device, base::NotFatalUntil::M158);
  CHECK(existing_request_state, base::NotFatalUntil::M158);

  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();
          bool is_same_device =
              device.id == hashed_source_id && device.type == new_device.type;
          if (base::FeatureList::IsEnabled(
                  kEnumerateDevicesUseNameInDeviceComparison)) {
            if (is_same_device) {
              base::UmaHistogramBoolean(
                  "Media.MediaDevices.FindExistingRequestedDevice.NameMatched",
                  device.name == new_device.name);
            }
            is_same_device = is_same_device && device.name == new_device.name;
          }
          // 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);
            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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(request, base::NotFatalUntil::M158);
  CHECK_EQ(request->request_type(), blink::MEDIA_GENERATE_STREAM,
           base::NotFatalUntil::M158);
  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.
  CHECK_EQ(1u, request->stream_devices_set.stream_devices.size(),
           base::NotFatalUntil::M158);

  // 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),
      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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(request, base::NotFatalUntil::M158);
  CHECK_EQ(request->request_type(), blink::MEDIA_GET_OPEN_DEVICE,
           base::NotFatalUntil::M158);
  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),
      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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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,
      base::ToString(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. Restore the request
      // state for the still-active devices so that later teardown closes
      // them as usual.
      if (blink::IsAudioInputMediaType(request->audio_type())) {
        request->SetState(request->audio_type(), MEDIA_REQUEST_STATE_DONE);
      }
      if (blink::IsVideoInputMediaType(request->video_type())) {
        request->SetState(request->video_type(), MEDIA_REQUEST_STATE_DONE);
      }
      CHECK_EQ(1u, request->stream_devices_set.stream_devices.size(),
               base::NotFatalUntil::M158);
      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();
        CHECK_NE(device.type, MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET,
                 base::NotFatalUntil::M158);
        // 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);
          CHECK(source.type == DesktopMediaID::TYPE_WEB_CONTENTS,
                base::NotFatalUntil::M158);
          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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(request, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  if (blink::IsAudioInputMediaType(device.type)) {
    target_devices.audio_device = device;
  } else {
    CHECK(blink::IsVideoInputMediaType(device.type), base::NotFatalUntil::M158);
    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;
  }

  // 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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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);
              device.input.set_effects(effects);
            }
          }
          if (RequestDone(*request)) {
            HandleRequestDone(label, request);
          }
          break;
        }
      }
    }
  }
}

void MediaStreamManager::HandleRequestDone(const std::string& label,
                                           DeviceRequest* request) {
  CHECK(RequestDone(*request), base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

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

    CHECK(request->audio_type() == stream_types[i] ||
              request->video_type() == stream_types[i],
          base::NotFatalUntil::M158);
    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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  SendLogMessage(base::StringPrintf(
      "Aborted({stream_type=%s}, {session_id=%s})",
      StreamTypeToString(stream_type), capture_session_id.ToString().c_str()));

  // If the video for a screen capture is aborted, the corresponding
  // audio must also be stopped.
  if (blink::IsVideoScreenCaptureMediaType(stream_type)) {
    SessionType actual_type;
    DeviceRequest* const request =
        FindRequestBySessionId(capture_session_id, &actual_type);
    if (request && actual_type == SessionType::kVideo) {
      for (const auto& stream_devices_ptr :
           request->stream_devices_set.stream_devices) {
        if (stream_devices_ptr->audio_device.has_value()) {
          VLOG(1) << "MSM::Aborted: Stopping associated audio device";
          StopDevice(stream_devices_ptr->audio_device->type,
                     stream_devices_ptr->audio_device->session_id());
        }
      }
    }
  }

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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,
    bool is_allowed_while_screen_locked) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK((result == MediaStreamRequestResult::OK &&
         !stream_devices_set.stream_devices.empty()) ||
            (result != MediaStreamRequestResult::OK &&
             stream_devices_set.stream_devices.empty()),
        base::NotFatalUntil::M158);

  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();
  request->is_allowed_while_screen_locked = is_allowed_while_screen_locked;

  SendLogMessage(base::StringPrintf(
      "HandleAccessRequestResponse({label=%s}, {request=%s}, {result=%s})",
      label.c_str(), RequestTypeToString(request->request_type()),
      base::ToString(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;
  }

  CHECK(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();
            }),
        base::NotFatalUntil::M158);

  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());
        CHECK(params.IsValid(), base::NotFatalUntil::M158);
        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);
    SendLogMessage(base::StringPrintf(
        "HandleAccessRequestResponse({label=%s}) => (Failing gUM "
        "request: No audio devices authorized/found)",
        label.c_str()));
  }

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK_EQ(request->stream_devices_set.stream_devices.size(), 1u,
           base::NotFatalUntil::M158);
  CHECK_LE(request->old_stream_devices_set.stream_devices.size(), 1u,
           base::NotFatalUntil::M158);
  CHECK_EQ(stream_devices_set.stream_devices.size(), 1u,
           base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  DeviceRequest* request = FindRequest(label);
  if (!request) {
    return;
  }

  CHECK_EQ(1u, request->stream_devices_set.stream_devices.size(),
           base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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()";
  CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO) ||
            !BrowserThread::IsThreadInitialized(BrowserThread::IO),
        base::NotFatalUntil::M158);
  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);
  }

#if BUILDFLAG(IS_CHROMEOS)
  // SystemEventMonitorImpl and JpegAcceleratorProviderImpl are created on the
  // UI thread and must be destroyed there before the UI message loop stops.
  if (system_event_monitor_) {
    GetUIThreadTaskRunner({})->DeleteSoon(FROM_HERE,
                                          std::move(system_event_monitor_));
  }
  if (jpeg_accelerator_provider_) {
    GetUIThreadTaskRunner({})->DeleteSoon(
        FROM_HERE, std::move(jpeg_accelerator_provider_));
  }
#endif

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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();
  }
}

// static
bool MediaStreamManager::RequestDone(const DeviceRequest& request) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  if (blink::IsAudioInputMediaType(request.audio_type()) &&
      request.state(request.audio_type()) != MEDIA_REQUEST_STATE_DONE &&
      request.state(request.audio_type()) != MEDIA_REQUEST_STATE_ERROR) {
    return false;
  }

  if (blink::IsVideoInputMediaType(request.video_type()) &&
      request.state(request.video_type()) != MEDIA_REQUEST_STATE_DONE &&
      request.state(request.video_type()) != MEDIA_REQUEST_STATE_ERROR) {
    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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  // 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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  log_callbacks_.erase(renderer_host_id);
}

// static
bool MediaStreamManager::IsOriginAllowed(ChildProcessId 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() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  for (LabeledDeviceRequest& labeled_request : requests_) {
    DeviceRequest* request = labeled_request.second.get();
    // TODO(crbug.com/379869738) Remove GetUnsafeValue.
    if (request->requesting_render_frame_host_id.child_id.GetUnsafeValue() !=
        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) {
  CHECK_LT(request_index, requests_.size(), base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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::OpenNativeScreenCapturePicker(
    DesktopMediaID::Type type,
    base::OnceCallback<void(DesktopMediaID::Id)> created_callback,
    base::OnceCallback<void(webrtc::DesktopCapturer::Source)> picker_callback,
    base::OnceCallback<void()> cancel_callback,
    base::OnceCallback<void()> error_callback) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  base::OnceCallback<void(DesktopMediaID::Id)> stop_audio_callback =
      base::BindPostTask(
          GetIOThreadTaskRunner({}),
          base::BindOnce(
              &MediaStreamManager::StopApplicationAudioForPickerSessionId,
              weak_ptr_factory_.GetWeakPtr()));

  video_capture_manager()->OpenNativeScreenCapturePicker(
      type, std::move(created_callback), std::move(picker_callback),
      std::move(cancel_callback), std::move(error_callback),
      std::move(stop_audio_callback));
}

void MediaStreamManager::StopApplicationAudioForPickerSessionId(
    DesktopMediaID::Id picker_session_id) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  base::UmaHistogramEnumeration(
      "Media.ScreenCaptureKit.SCContentSharingPicker.StopAudioEvent",
      StopAudioEvent::kStopAudioCallbackCalled);

  for (const auto& [label, request] : requests_) {
    for (const auto& stream_devices_ptr :
         request->stream_devices_set.stream_devices) {
      if (stream_devices_ptr->video_device.has_value() &&
          blink::IsDesktopCaptureMediaType(
              stream_devices_ptr->video_device->type) &&
          DesktopMediaID::Parse(stream_devices_ptr->video_device->id).id ==
              picker_session_id) {
        const bool is_application_audio =
            stream_devices_ptr->audio_device.has_value() &&
            request->audio_raw_id().has_value() &&
            media::AudioDeviceDescription::IsApplicationLoopbackDevice(
                *request->audio_raw_id());

        if (is_application_audio) {
          base::UmaHistogramEnumeration(
              "Media.ScreenCaptureKit.SCContentSharingPicker.StopAudioEvent",
              StopAudioEvent::kApplicationAudioStopped);

          StopDevice(stream_devices_ptr->audio_device->type,
                     stream_devices_ptr->audio_device->session_id());
        }
        return;
      }
    }
  }
}

void MediaStreamManager::OnRegionCaptureRectChanged(
    const base::UnguessableToken& session_id,
    const std::optional<gfx::Rect>& region_capture_rect) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  DeviceRequest* const request = FindRequest(label);
  if (!request) {
    return;
  }

  if (!request->ui_proxy) {
    return;
  }

  CHECK_EQ(1u, request->stream_devices_set.stream_devices.size(),
           base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  SessionType actual_type;
  DeviceRequest* const request =
      FindRequestBySessionId(session_id, &actual_type);
  if (!request || actual_type != SessionType::kVideo) {
    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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  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) {
  SessionType actual_type;
  DeviceRequest* request = FindRequestBySessionId(session_id, &actual_type);
  if (request == nullptr || actual_type != SessionType::kVideo) {
    return std::nullopt;
  }
  return request->salt_and_origin.origin();
}

bool MediaStreamManager::IsSessionAllowedOnLockScreen(
    const base::UnguessableToken& session_id) {
  SessionType actual_type;
  DeviceRequest* request = FindRequestBySessionId(session_id, &actual_type);
  if (request == nullptr || actual_type != SessionType::kVideo) {
    return false;
  }
  return request->is_allowed_while_screen_locked;
}

// static
PermissionControllerImpl* MediaStreamManager::GetPermissionController(
    GlobalRenderFrameHostId requesting_render_frame_host_id) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK(request, base::NotFatalUntil::M158);

  // 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) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);

  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->SubscribeToPermissionResultChange(
        PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
            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->SubscribeToPermissionResultChange(
        PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType(
            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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  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) {
  CHECK_CURRENTLY_ON(BrowserThread::UI, base::NotFatalUntil::M158);

  PermissionControllerImpl* controller =
      GetPermissionController(requesting_render_frame_host_id);
  if (!controller) {
    return;
  }

  controller->UnsubscribeFromPermissionResultChange(audio_subscription_id);
  controller->UnsubscribeFromPermissionResultChange(video_subscription_id);
}

void MediaStreamManager::PermissionChangedCallback(
    GlobalRenderFrameHostId requesting_render_frame_host_id,
    int requester_id,
    int page_request_id,
    PermissionResult permission_result) {
  if (permission_result.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, permission_result));

    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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  if (!blink::IsVideoInputMediaType(captured_device.type) ||
      !IsEligibleForCaptureHandle(captured_device.id)) {
    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::OnCaptureStarted,
                     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) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);

  if (!blink::IsVideoInputMediaType(captured_device.type) ||
      !IsEligibleForCaptureHandle(captured_device.id)) {
    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::OnCaptureStopped,
                                base::Unretained(&capture_handle_manager_),
                                label, captured_device));
}

void MediaStreamManager::MaybeUpdateTrackedCaptureHandleConfigs(
    const std::string& label,
    const blink::mojom::StreamDevicesSet& new_devices_set,
    DeviceRequest& request) {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  CHECK_EQ(1u, new_devices_set.stream_devices.size(),
           base::NotFatalUntil::M158);

  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() &&
      IsEligibleForCaptureHandle(new_devices.video_device->id)) {
    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::OnCaptureDevicesUpdated,
                     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() {
  CHECK_CURRENTLY_ON(BrowserThread::IO, base::NotFatalUntil::M158);
  media_devices_manager_->UpdateVideoCaptureHostsEmptyState(
      video_capture_hosts_.empty());
}
#endif

}  // namespace content
