text: // Copyright 2017 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "third_party/blink/renderer/modules/mediastream/user_media_processor.h"
  
  #include <stddef.h>
  
  #include <algorithm>
  #include <utility>
  #include <vector>
  
  #include "base/callback_helpers.h"
  #include "base/containers/contains.h"
  #include "base/location.h"
  #include "base/logging.h"
  #include "base/stl_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/task/single_thread_task_runner.h"
  #include "build/build_config.h"
  #include "media/base/audio_parameters.h"
  #include "media/capture/video_capture_types.h"
  #include "media/webrtc/constants.h"
  #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
  #include "third_party/blink/public/common/mediastream/media_stream_controls.h"
  #include "third_party/blink/public/common/mediastream/media_stream_request.h"
  #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_source.h"
  #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
  #include "third_party/blink/public/platform/modules/webrtc/webrtc_logging.h"
  #include "third_party/blink/public/platform/web_string.h"
  #include "third_party/blink/public/platform/web_vector.h"
  #include "third_party/blink/public/web/modules/mediastream/web_media_stream_device_observer.h"
  #include "third_party/blink/public/web/web_local_frame.h"
  #include "third_party/blink/public/web/web_local_frame_client.h"
  #include "third_party/blink/renderer/core/frame/local_dom_window.h"
  #include "third_party/blink/renderer/core/frame/local_frame.h"
  #include "third_party/blink/renderer/core/page/chrome_client.h"
  #include "third_party/blink/renderer/modules/mediastream/local_media_stream_audio_source.h"
  #include "third_party/blink/renderer/modules/mediastream/local_video_capturer_source.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_audio_processor.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_audio.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_content.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_device.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_video_capturer_source.h"
  #include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
  #include "third_party/blink/renderer/modules/mediastream/processed_local_audio_source.h"
  #include "third_party/blink/renderer/modules/mediastream/user_media_client.h"
  #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
  #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
  #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
  #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
  #include "third_party/blink/renderer/platform/mediastream/webrtc_uma_histograms.h"
  #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
  #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
  #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
  #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
  #include "ui/display/screen_info.h"
  #include "ui/gfx/geometry/size.h"
  
  namespace blink {
  
  using blink::mojom::MediaStreamRequestResult;
  using blink::mojom::MediaStreamType;
  using blink::mojom::StreamSelectionStrategy;
  using EchoCancellationType =
      blink::AudioProcessingProperties::EchoCancellationType;
  using AudioSourceErrorCode = media::AudioCapturerSource::ErrorCode;
  
  namespace {
  
  const char* MediaStreamRequestResultToString(MediaStreamRequestResult value) {
    switch (value) {
      case MediaStreamRequestResult::OK:
        return "OK";
      case MediaStreamRequestResult::PERMISSION_DENIED:
        return "PERMISSION_DENIED";
      case MediaStreamRequestResult::PERMISSION_DISMISSED:
        return "PERMISSION_DISMISSED";
      case MediaStreamRequestResult::INVALID_STATE:
        return "INVALID_STATE";
      case MediaStreamRequestResult::NO_HARDWARE:
        return "NO_HARDWARE";
      case MediaStreamRequestResult::INVALID_SECURITY_ORIGIN:
        return "INVALID_SECURITY_ORIGIN";
      case MediaStreamRequestResult::TAB_CAPTURE_FAILURE:
        return "TAB_CAPTURE_FAILURE";
      case MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE:
        return "SCREEN_CAPTURE_FAILURE";
      case MediaStreamRequestResult::CAPTURE_FAILURE:
        return "CAPTURE_FAILURE";
      case MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED:
        return "CONSTRAINT_NOT_SATISFIED";
      case MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO:
        return "TRACK_START_FAILURE_AUDIO";
      case MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO:
        return "TRACK_START_FAILURE_VIDEO";
      case MediaStreamRequestResult::NOT_SUPPORTED:
        return "NOT_SUPPORTED";
      case MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN:
        return "FAILED_DUE_TO_SHUTDOWN";
      case MediaStreamRequestResult::KILL_SWITCH_ON:
        return "KILL_SWITCH_ON";
      case MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED:
        return "SYSTEM_PERMISSION_DENIED";
      case MediaStreamRequestResult::DEVICE_IN_USE:
        return "DEVICE_IN_USE";
      case MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS:
        return "NUM_MEDIA_REQUEST_RESULTS";
      default:
        NOTREACHED();
    }
    return "INVALID";
  }
  
  void SendLogMessage(const std::string& message) {
    blink::WebRtcLogMessage("UMP::" + message);
  }
  
  std::string GetTrackLogString(MediaStreamComponent* component,
                                bool is_pending) {
    String str = String::Format(
        "StartAudioTrack({track=[id: %s, enabled: %d, muted: %d]}, "
        "{is_pending=%d})",
        component->Id().Utf8().c_str(), component->Enabled(), component->Muted(),
        is_pending);
    return str.Utf8();
  }
  
  std::string GetTrackSourceLogString(blink::MediaStreamAudioSource* source) {
    const MediaStreamDevice& device = source->device();
    StringBuilder builder;
    builder.AppendFormat("StartAudioTrack(source: {session_id=%s}, ",
                         device.session_id().ToString().c_str());
    builder.AppendFormat("{is_local_source=%d}, ", source->is_local_source());
    builder.AppendFormat("{device=[id: %s", device.id.c_str());
    if (device.group_id.has_value()) {
      builder.AppendFormat(", group_id: %s", device.group_id.value().c_str());
    }
    builder.AppendFormat(", name: %s", device.name.c_str());
    builder.Append(String("]})"));
    return builder.ToString().Utf8();
  }
  
  std::string GetOnTrackStartedLogString(
      blink::WebPlatformMediaStreamSource* source,
      MediaStreamRequestResult result) {
    const MediaStreamDevice& device = source->device();
    String str = String::Format("OnTrackStarted({session_id=%s}, {result=%s})",
                                device.session_id().ToString().c_str(),
                                MediaStreamRequestResultToString(result));
    return str.Utf8();
  }
  
  void InitializeAudioTrackControls(UserMediaRequest* user_media_request,
                                    TrackControls* track_controls) {
    if (user_media_request->MediaRequestType() ==
        UserMediaRequest::MediaType::kDisplayMedia) {
      track_controls->requested = true;
      track_controls->stream_type = MediaStreamType::DISPLAY_AUDIO_CAPTURE;
      return;
    }
  
    DCHECK_EQ(UserMediaRequest::MediaType::kUserMedia,
              user_media_request->MediaRequestType());
    const MediaConstraints& constraints = user_media_request->AudioConstraints();
    DCHECK(!constraints.IsNull());
    track_controls->requested = true;
  
    MediaStreamType* stream_type = &track_controls->stream_type;
    *stream_type = MediaStreamType::NO_SERVICE;
  
    String source_constraint =
        constraints.Basic().media_stream_source.Exact().IsEmpty()
            ? String()
            : String(constraints.Basic().media_stream_source.Exact()[0]);
    if (!source_constraint.IsEmpty()) {
      if (source_constraint == blink::kMediaStreamSourceTab) {
        *stream_type = MediaStreamType::GUM_TAB_AUDIO_CAPTURE;
      } else if (source_constraint == blink::kMediaStreamSourceDesktop ||
                 source_constraint == blink::kMediaStreamSourceSystem) {
        *stream_type = MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
      }
    } else {
      *stream_type = MediaStreamType::DEVICE_AUDIO_CAPTURE;
    }
  }
  
  void InitializeVideoTrackControls(UserMediaRequest* user_media_request,
                                    TrackControls* track_controls) {
    if (user_media_request->MediaRequestType() ==
        UserMediaRequest::MediaType::kDisplayMedia) {
      track_controls->requested = true;
      track_controls->stream_type =
          user_media_request->should_prefer_current_tab()
              ? MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB
              : MediaStreamType::DISPLAY_VIDEO_CAPTURE;
      return;
    }
  
    DCHECK_EQ(UserMediaRequest::MediaType::kUserMedia,
              user_media_request->MediaRequestType());
    const MediaConstraints& constraints = user_media_request->VideoConstraints();
    DCHECK(!constraints.IsNull());
    track_controls->requested = true;
  
    MediaStreamType* stream_type = &track_controls->stream_type;
    *stream_type = MediaStreamType::NO_SERVICE;
  
    String source_constraint =
        constraints.Basic().media_stream_source.Exact().IsEmpty()
            ? String()
            : String(constraints.Basic().media_stream_source.Exact()[0]);
    if (!source_constraint.IsEmpty()) {
      if (source_constraint == blink::kMediaStreamSourceTab) {
        *stream_type = MediaStreamType::GUM_TAB_VIDEO_CAPTURE;
      } else if (source_constraint == blink::kMediaStreamSourceDesktop ||
                 source_constraint == blink::kMediaStreamSourceScreen) {
        *stream_type = MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
      }
    } else {
      *stream_type = MediaStreamType::DEVICE_VIDEO_CAPTURE;
    }
  }
  
  bool IsSameDevice(const MediaStreamDevice& device,
                    const MediaStreamDevice& other_device) {
    return device.id == other_device.id && device.type == other_device.type &&
           device.session_id() == other_device.session_id();
  }
  
  bool IsSameSource(MediaStreamSource* source, MediaStreamSource* other_source) {
    WebPlatformMediaStreamSource* const source_extra_data =
        source->GetPlatformSource();
    const MediaStreamDevice& device = source_extra_data->device();
  
    WebPlatformMediaStreamSource* const other_source_extra_data =
        other_source->GetPlatformSource();
    const MediaStreamDevice& other_device = other_source_extra_data->device();
  
    return IsSameDevice(device, other_device);
  }
  
  void SurfaceAudioProcessingSettings(MediaStreamSource* source) {
    auto* source_impl =
        static_cast<blink::MediaStreamAudioSource*>(source->GetPlatformSource());
  
    // If the source is a processed source, get the properties from it.
    if (auto* processed_source =
            blink::ProcessedLocalAudioSource::From(source_impl)) {
      blink::AudioProcessingProperties properties =
          processed_source->audio_processing_properties();
      MediaStreamSource::EchoCancellationMode echo_cancellation_mode;
  
      switch (properties.echo_cancellation_type) {
        case EchoCancellationType::kEchoCancellationDisabled:
          echo_cancellation_mode =
              MediaStreamSource::EchoCancellationMode::kDisabled;
          break;
        case EchoCancellationType::kEchoCancellationAec3:
          echo_cancellation_mode =
              MediaStreamSource::EchoCancellationMode::kBrowser;
          break;
        case EchoCancellationType::kEchoCancellationSystem:
          echo_cancellation_mode =
              MediaStreamSource::EchoCancellationMode::kSystem;
          break;
      }
  
      source->SetAudioProcessingProperties(echo_cancellation_mode,
                                           properties.goog_auto_gain_control,
                                           properties.goog_noise_suppression);
    } else {
      // If the source is not a processed source, it could still support system
      // echo cancellation. Surface that if it does.
      media::AudioParameters params = source_impl->GetAudioParameters();
      const MediaStreamSource::EchoCancellationMode echo_cancellation_mode =
          params.IsValid() &&
                  (params.effects() & media::AudioParameters::ECHO_CANCELLER)
              ? MediaStreamSource::EchoCancellationMode::kSystem
              : MediaStreamSource::EchoCancellationMode::kDisabled;
  
      source->SetAudioProcessingProperties(echo_cancellation_mode, false, false);
    }
  }
  
  // TODO(crbug.com/704136): Check all places where this helper is used.
  // Change their types from using std::vector to WTF::Vector, so this
  // extra conversion round is not needed.
  template <typename T>
  std::vector<T> ToStdVector(const Vector<T>& format_vector) {
    std::vector<T> formats;
    std::copy(format_vector.begin(), format_vector.end(),
              std::back_inserter(formats));
    return formats;
  }
  
  Vector<blink::VideoInputDeviceCapabilities> ToVideoInputDeviceCapabilities(
      const Vector<blink::mojom::blink::VideoInputDeviceCapabilitiesPtr>&
          input_capabilities) {
    Vector<blink::VideoInputDeviceCapabilities> capabilities;
    for (const auto& capability : input_capabilities) {
      capabilities.emplace_back(capability->device_id, capability->group_id,
                                capability->control_support, capability->formats,
                                capability->facing_mode);
    }
  
    return capabilities;
  }
  
  }  // namespace
  
  // Class for storing state of the the processing of getUserMedia requests.
  class UserMediaProcessor::RequestInfo final
      : public GarbageCollected<UserMediaProcessor::RequestInfo> {
   public:
    using ResourcesReady =
        base::OnceCallback<void(RequestInfo* request_info,
                                MediaStreamRequestResult result,
                                const String& result_name)>;
    enum class State {
      kNotSentForGeneration,
      kSentForGeneration,
      kGenerated,
    };
  
    explicit RequestInfo(UserMediaRequest* request);
  
    void StartAudioTrack(MediaStreamComponent* component, bool is_pending);
    MediaStreamComponent* CreateAndStartVideoTrack(MediaStreamSource* source);
  
    // Triggers |callback| when all sources used in this request have either
    // successfully started, or a source has failed to start.
    void CallbackOnTracksStarted(ResourcesReady callback);
  
    // Called when a local audio source has finished (or failed) initializing.
    void OnAudioSourceStarted(blink::WebPlatformMediaStreamSource* source,
                              MediaStreamRequestResult result,
                              const String& result_name);
  
    UserMediaRequest* request() { return request_; }
    int request_id() const { return request_->request_id(); }
  
    State state() const { return state_; }
    void set_state(State state) { state_ = state; }
  
    const blink::AudioCaptureSettings& audio_capture_settings() const {
      return audio_capture_settings_;
    }
    void SetAudioCaptureSettings(const blink::AudioCaptureSettings& settings,
                                 bool is_content_capture) {
      DCHECK(settings.HasValue());
      is_audio_content_capture_ = is_content_capture;
      audio_capture_settings_ = settings;
    }
    const blink::VideoCaptureSettings& video_capture_settings() const {
      return video_capture_settings_;
    }
    bool is_video_content_capture() const {
      return video_capture_settings_.HasValue() && is_video_content_capture_;
    }
    bool is_video_device_capture() const {
      return video_capture_settings_.HasValue() && !is_video_content_capture_;
    }
    void SetVideoCaptureSettings(const blink::VideoCaptureSettings& settings,
                                 bool is_content_capture) {
      DCHECK(settings.HasValue());
      is_video_content_capture_ = is_content_capture;
      video_capture_settings_ = settings;
    }
  
    void SetDevices(Vector<MediaStreamDevice> audio_devices,
                    Vector<MediaStreamDevice> video_devices) {
      audio_devices_ = std::move(audio_devices);
      video_devices_ = std::move(video_devices);
    }
  
    void AddNativeVideoFormats(const String& device_id,
                               Vector<media::VideoCaptureFormat> formats) {
      video_formats_map_.insert(device_id, std::move(formats));
    }
  
    // Do not store or delete the returned pointer.
    Vector<media::VideoCaptureFormat>* GetNativeVideoFormats(
        const String& device_id) {
      auto it = video_formats_map_.find(device_id);
      CHECK(it != video_formats_map_.end());
      return &it->value;
    }
  
    void InitializeWebStream(const String& label,
                             const MediaStreamComponentVector& audios,
                             const MediaStreamComponentVector& videos) {
      descriptor_ =
          MakeGarbageCollected<MediaStreamDescriptor>(label, audios, videos);
    }
  
    const Vector<MediaStreamDevice>& audio_devices() const {
      return audio_devices_;
    }
    const Vector<MediaStreamDevice>& video_devices() const {
      return video_devices_;
    }
  
    bool CanStartTracks() const {
      return video_formats_map_.size() == video_devices_.size();
    }
  
    MediaStreamDescriptor* descriptor() {
      DCHECK(descriptor_);
      return descriptor_;
    }
  
    StreamControls* stream_controls() { return &stream_controls_; }
  
    bool is_processing_user_gesture() const {
      return request_->has_transient_user_activation();
    }
  
    bool pan_tilt_zoom_allowed() const { return pan_tilt_zoom_allowed_; }
    void set_pan_tilt_zoom_allowed(bool pan_tilt_zoom_allowed) {
      pan_tilt_zoom_allowed_ = pan_tilt_zoom_allowed;
    }
  
    void Trace(Visitor* visitor) const {
      visitor->Trace(request_);
      visitor->Trace(descriptor_);
      visitor->Trace(sources_);
    }
  
   private:
    void OnTrackStarted(blink::WebPlatformMediaStreamSource* source,
                        MediaStreamRequestResult result,
                        const blink::WebString& result_name);
  
    // Checks if the sources for all tracks have been started and if so,
    // invoke the |ready_callback_|.  Note that the caller should expect
    // that |this| might be deleted when the function returns.
    void CheckAllTracksStarted();
  
    Member<UserMediaRequest> request_;
    State state_ = State::kNotSentForGeneration;
    blink::AudioCaptureSettings audio_capture_settings_;
    bool is_audio_content_capture_ = false;
    blink::VideoCaptureSettings video_capture_settings_;
    bool is_video_content_capture_ = false;
    Member<MediaStreamDescriptor> descriptor_;
    StreamControls stream_controls_;
    ResourcesReady ready_callback_;
    MediaStreamRequestResult request_result_ = MediaStreamRequestResult::OK;
    String request_result_name_;
    // Sources used in this request.
    HeapVector<Member<MediaStreamSource>> sources_;
    Vector<blink::WebPlatformMediaStreamSource*> sources_waiting_for_callback_;
    HashMap<String, Vector<media::VideoCaptureFormat>> video_formats_map_;
    Vector<MediaStreamDevice> audio_devices_;
    Vector<MediaStreamDevice> video_devices_;
    bool pan_tilt_zoom_allowed_ = false;
  };
  
  // TODO(guidou): Initialize request_result_name_ as a null WTF::String.
  // https://crbug.com/764293
  UserMediaProcessor::RequestInfo::RequestInfo(UserMediaRequest* request)
      : request_(request), request_result_name_("") {}
  
  void UserMediaProcessor::RequestInfo::StartAudioTrack(
      MediaStreamComponent* component,
      bool is_pending) {
    DCHECK(component->Source()->GetType() == MediaStreamSource::kTypeAudio);
    DCHECK(request()->Audio());
  #if DCHECK_IS_ON()
    DCHECK(audio_capture_settings_.HasValue());
  #endif
    SendLogMessage(GetTrackLogString(component, is_pending));
    auto* native_source = MediaStreamAudioSource::From(component->Source());
    SendLogMessage(GetTrackSourceLogString(native_source));
    // Add the source as pending since OnTrackStarted will expect it to be there.
    sources_waiting_for_callback_.push_back(native_source);
  
    sources_.push_back(component->Source());
    bool connected = native_source->ConnectToTrack(component);
    if (!is_pending) {
      OnTrackStarted(native_source,
                     connected
                         ? MediaStreamRequestResult::OK
                         : MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO,
                     "");
    }
  }
  
  MediaStreamComponent* UserMediaProcessor::RequestInfo::CreateAndStartVideoTrack(
      MediaStreamSource* source) {
    DCHECK(source->GetType() == MediaStreamSource::kTypeVideo);
    DCHECK(request()->Video());
    DCHECK(video_capture_settings_.HasValue());
    SendLogMessage(base::StringPrintf(
        "UMP::RI::CreateAndStartVideoTrack({request_id=%d})", request_id()));
  
    MediaStreamVideoSource* native_source =
        MediaStreamVideoSource::GetVideoSource(source);
    DCHECK(native_source);
    sources_.push_back(source);
    sources_waiting_for_callback_.push_back(native_source);
    return MediaStreamVideoTrack::CreateVideoTrack(
        native_source, video_capture_settings_.track_adapter_settings(),
        video_capture_settings_.noise_reduction(), is_video_content_capture_,
        video_capture_settings_.min_frame_rate(), video_capture_settings_.pan(),
        video_capture_settings_.tilt(), video_capture_settings_.zoom(),
        pan_tilt_zoom_allowed(),
        WTF::Bind(&UserMediaProcessor::RequestInfo::OnTrackStarted,
                  WrapWeakPersistent(this)),
        true);
  }
  
  void UserMediaProcessor::RequestInfo::CallbackOnTracksStarted(
      ResourcesReady callback) {
    DCHECK(ready_callback_.is_null());
    ready_callback_ = std::move(callback);
    CheckAllTracksStarted();
  }
  
  void UserMediaProcessor::RequestInfo::OnTrackStarted(
      blink::WebPlatformMediaStreamSource* source,
      MediaStreamRequestResult result,
      const blink::WebString& result_name) {
    SendLogMessage(GetOnTrackStartedLogString(source, result));
    auto** it = std::find(sources_waiting_for_callback_.begin(),
                          sources_waiting_for_callback_.end(), source);
    DCHECK(it != sources_waiting_for_callback_.end());
    sources_waiting_for_callback_.erase(it);
    // All tracks must be started successfully. Otherwise the request is a
    // failure.
    if (result != MediaStreamRequestResult::OK) {
      request_result_ = result;
      request_result_name_ = result_name;
    }
  
    CheckAllTracksStarted();
  }
  
  void UserMediaProcessor::RequestInfo::CheckAllTracksStarted() {
    if (ready_callback_ && sources_waiting_for_callback_.IsEmpty()) {
      std::move(ready_callback_).Run(this, request_result_, request_result_name_);
      // NOTE: |this| might now be deleted.
    }
  }
  
  void UserMediaProcessor::RequestInfo::OnAudioSourceStarted(
      blink::WebPlatformMediaStreamSource* source,
      MediaStreamRequestResult result,
      const String& result_name) {
    // Check if we're waiting to be notified of this source.  If not, then we'll
    // ignore the notification.
    if (base::Contains(sources_waiting_for_callback_, source))
      OnTrackStarted(source, result, result_name);
  }
  
  UserMediaProcessor::UserMediaProcessor(
      LocalFrame* frame,
      MediaDevicesDispatcherCallback media_devices_dispatcher_cb,
      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
      : dispatcher_host_(frame->DomWindow()),
        media_devices_dispatcher_cb_(std::move(media_devices_dispatcher_cb)),
        frame_(frame),
        task_runner_(std::move(task_runner)) {}
  
  UserMediaProcessor::~UserMediaProcessor() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  
    // Ensure StopAllProcessing() has been called by UserMediaClient.
    DCHECK(!current_request_info_ && !request_completed_cb_ &&
           !local_sources_.size());
  }
  
  UserMediaRequest* UserMediaProcessor::CurrentRequest() {
    return current_request_info_ ? current_request_info_->request() : nullptr;
  }
  
  void UserMediaProcessor::ProcessRequest(UserMediaRequest* request,
                                          base::OnceClosure callback) {
    DCHECK(!request_completed_cb_);
    DCHECK(!current_request_info_);
    request_completed_cb_ = std::move(callback);
    current_request_info_ = MakeGarbageCollected<RequestInfo>(request);
    SendLogMessage(
        base::StringPrintf("ProcessRequest({request_id=%d}, {audio=%d}, "
                           "{video=%d})",
                           current_request_info_->request_id(),
                           current_request_info_->request()->Audio(),
                           current_request_info_->request()->Video()));
    // TODO(guidou): Set up audio and video in parallel.
    if (current_request_info_->request()->Audio()) {
      SetupAudioInput();
      return;
    }
    SetupVideoInput();
  }
  
  void UserMediaProcessor::SetupAudioInput() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    DCHECK(current_request_info_->request()->Audio());
    SendLogMessage(
        base::StringPrintf("SetupAudioInput({request_id=%d}, {constraints=%s})",
                           current_request_info_->request_id(),
                           current_request_info_->request()
                               ->AudioConstraints()
                               .ToString()
                               .Utf8()
                               .c_str()));
  
    auto& audio_controls = current_request_info_->stream_controls()->audio;
    InitializeAudioTrackControls(current_request_info_->request(),
                                 &audio_controls);
  
    if (audio_controls.stream_type == MediaStreamType::DISPLAY_AUDIO_CAPTURE) {
      SelectAudioSettings(current_request_info_->request(),
                          {blink::AudioDeviceCaptureCapability()});
      return;
    }
  
    if (blink::IsDeviceMediaType(audio_controls.stream_type)) {
      SendLogMessage(
          base::StringPrintf("SetupAudioInput({request_id=%d}) => "
                             "(Requesting device capabilities)",
                             current_request_info_->request_id()));
      GetMediaDevicesDispatcher()->GetAudioInputCapabilities(
          WTF::Bind(&UserMediaProcessor::SelectAudioDeviceSettings,
                    WrapWeakPersistent(this),
                    WrapPersistent(current_request_info_->request())));
    } else {
      if (!blink::IsAudioInputMediaType(audio_controls.stream_type)) {
        String failed_constraint_name =
            String(current_request_info_->request()
                       ->AudioConstraints()
                       .Basic()
                       .media_stream_source.GetName());
        MediaStreamRequestResult result =
            MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED;
        GetUserMediaRequestFailed(result, failed_constraint_name);
        return;
      }
      SelectAudioSettings(current_request_info_->request(),
                          {blink::AudioDeviceCaptureCapability()});
    }
  }
  
  void UserMediaProcessor::SelectAudioDeviceSettings(
      UserMediaRequest* user_media_request,
      Vector<blink::mojom::blink::AudioInputDeviceCapabilitiesPtr>
          audio_input_capabilities) {
    blink::AudioDeviceCaptureCapabilities capabilities;
    for (const auto& device : audio_input_capabilities) {
      // Find the first occurrence of blink::ProcessedLocalAudioSource that
      // matches the same device ID as |device|. If more than one exists, any
      // such source will contain the same non-reconfigurable settings that limit
      // the associated capabilities.
      blink::MediaStreamAudioSource* audio_source = nullptr;
      auto* it = std::find_if(local_sources_.begin(), local_sources_.end(),
                              [&device](MediaStreamSource* source) {
                                DCHECK(source);
                                MediaStreamAudioSource* platform_source =
                                    MediaStreamAudioSource::From(source);
                                ProcessedLocalAudioSource* processed_source =
                                    ProcessedLocalAudioSource::From(
                                        platform_source);
                                return processed_source &&
                                       source->Id() == device->device_id;
                              });
      if (it != local_sources_.end()) {
        WebPlatformMediaStreamSource* const source = (*it)->GetPlatformSource();
        if (source->device().type == MediaStreamType::DEVICE_AUDIO_CAPTURE)
          audio_source = static_cast<MediaStreamAudioSource*>(source);
      }
      if (audio_source) {
        capabilities.emplace_back(audio_source);
      } else {
        capabilities.emplace_back(device->device_id, device->group_id,
                                  device->parameters);
      }
    }
  
    SelectAudioSettings(user_media_request, capabilities);
  }
  
  void UserMediaProcessor::SelectAudioSettings(
      UserMediaRequest* user_media_request,
      const blink::AudioDeviceCaptureCapabilities& capabilities) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    // The frame might reload or |user_media_request| might be cancelled while
    // capabilities are queried. Do nothing if a different request is being
    // processed at this point.
    if (!IsCurrentRequestInfo(user_media_request))
      return;
  
    DCHECK(current_request_info_->stream_controls()->audio.requested);
    SendLogMessage(base::StringPrintf("SelectAudioSettings({request_id=%d})",
                                      current_request_info_->request_id()));
    auto settings = SelectSettingsAudioCapture(
        capabilities, user_media_request->AudioConstraints(),
        user_media_request->ShouldDisableHardwareNoiseSuppression(),
        true /* is_reconfiguration_allowed */);
    if (!settings.HasValue()) {
      String failed_constraint_name = String(settings.failed_constraint_name());
      MediaStreamRequestResult result =
          failed_constraint_name.IsEmpty()
              ? MediaStreamRequestResult::NO_HARDWARE
              : MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED;
      GetUserMediaRequestFailed(result, failed_constraint_name);
      return;
    }
    if (current_request_info_->stream_controls()->audio.stream_type !=
        MediaStreamType::DISPLAY_AUDIO_CAPTURE) {
      current_request_info_->stream_controls()->audio.device_id =
          settings.device_id();
      current_request_info_->stream_controls()->disable_local_echo =
          settings.disable_local_echo();
    }
    current_request_info_->SetAudioCaptureSettings(
        settings,
        !blink::IsDeviceMediaType(
            current_request_info_->stream_controls()->audio.stream_type));
  
    // No further audio setup required. Continue with video.
    SetupVideoInput();
  }
  
  absl::optional<base::UnguessableToken>
  UserMediaProcessor::DetermineExistingAudioSessionId() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_->request()->Audio());
  
    auto settings = current_request_info_->audio_capture_settings();
    auto device_id = settings.device_id();
  
    // Create a copy of the MediaStreamSource objects that are
    // associated to the same audio device capture based on its device ID.
    HeapVector<Member<MediaStreamSource>> matching_sources;
  
    // Take a defensive copy, as local_sources_ can be modified during
    // destructions in GC runs triggered by the push_back allocation in this loop.
    // crbug.com/1238209
    HeapVector<Member<MediaStreamSource>> local_sources_copy = local_sources_;
    for (const auto& source : local_sources_copy) {
      MediaStreamSource* source_copy = source;
      if (source_copy->GetType() == MediaStreamSource::kTypeAudio &&
          source_copy->Id().Utf8() == device_id) {
        matching_sources.push_back(source_copy);
      }
    }
  
    // Return the session ID associated to the source that has the same settings
    // that have been previously selected, if one exists.
    if (!matching_sources.IsEmpty()) {
      for (auto& matching_source : matching_sources) {
        auto* audio_source = static_cast<MediaStreamAudioSource*>(
            matching_source->GetPlatformSource());
        if (audio_source->HasSameReconfigurableSettings(
                settings.audio_processing_properties())) {
          return audio_source->device().session_id();
        }
      }
    }
  
    return absl::nullopt;
  }
  
  void UserMediaProcessor::SetupVideoInput() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
  
    if (!current_request_info_->request()->Video()) {
      absl::optional<base::UnguessableToken> audio_session_id =
          DetermineExistingAudioSessionId();
      GenerateStreamForCurrentRequestInfo(
          audio_session_id, audio_session_id.has_value()
                                ? StreamSelectionStrategy::SEARCH_BY_SESSION_ID
                                : StreamSelectionStrategy::FORCE_NEW_STREAM);
      return;
    }
    SendLogMessage(
        base::StringPrintf("SetupVideoInput. request_id=%d, video constraints=%s",
                           current_request_info_->request_id(),
                           current_request_info_->request()
                               ->VideoConstraints()
                               .ToString()
                               .Utf8()
                               .c_str()));
  
    auto& video_controls = current_request_info_->stream_controls()->video;
    InitializeVideoTrackControls(current_request_info_->request(),
                                 &video_controls);
  
    current_request_info_->stream_controls()->request_pan_tilt_zoom_permission =
        IsPanTiltZoomPermissionRequested(
            current_request_info_->request()->VideoConstraints());
  
    current_request_info_->stream_controls()->region_capture_capable =
        current_request_info_->request()->region_capture_capable();
  
    if (blink::IsDeviceMediaType(video_controls.stream_type)) {
      GetMediaDevicesDispatcher()->GetVideoInputCapabilities(
          WTF::Bind(&UserMediaProcessor::SelectVideoDeviceSettings,
                    WrapWeakPersistent(this),
                    WrapPersistent(current_request_info_->request())));
    } else {
      if (!blink::IsVideoInputMediaType(video_controls.stream_type)) {
        String failed_constraint_name =
            String(current_request_info_->request()
                       ->VideoConstraints()
                       .Basic()
                       .media_stream_source.GetName());
        MediaStreamRequestResult result =
            MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED;
        GetUserMediaRequestFailed(result, failed_constraint_name);
        return;
      }
      SelectVideoContentSettings();
    }
  }
  
  // static
  bool UserMediaProcessor::IsPanTiltZoomPermissionRequested(
      const MediaConstraints& constraints) {
    if (constraints.Basic().pan.IsPresent() ||
        constraints.Basic().tilt.IsPresent() ||
        constraints.Basic().zoom.IsPresent()) {
      return true;
    }
  
    for (const auto& advanced_set : constraints.Advanced()) {
      if (advanced_set.pan.IsPresent() || advanced_set.tilt.IsPresent() ||
          advanced_set.zoom.IsPresent()) {
        return true;
      }
    }
  
    return false;
  }
  
  void UserMediaProcessor::SelectVideoDeviceSettings(
      UserMediaRequest* user_media_request,
      Vector<blink::mojom::blink::VideoInputDeviceCapabilitiesPtr>
          video_input_capabilities) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    // The frame might reload or |user_media_request| might be cancelled while
    // capabilities are queried. Do nothing if a different request is being
    // processed at this point.
    if (!IsCurrentRequestInfo(user_media_request))
      return;
  
    DCHECK(current_request_info_->stream_controls()->video.requested);
    DCHECK(blink::IsDeviceMediaType(
        current_request_info_->stream_controls()->video.stream_type));
    SendLogMessage(base::StringPrintf("SelectVideoDeviceSettings. request_id=%d.",
                                      current_request_info_->request_id()));
  
    blink::VideoDeviceCaptureCapabilities capabilities;
    capabilities.device_capabilities =
        ToVideoInputDeviceCapabilities(video_input_capabilities);
    capabilities.noise_reduction_capabilities = {absl::optional<bool>(),
                                                 absl::optional<bool>(true),
                                                 absl::optional<bool>(false)};
    blink::VideoCaptureSettings settings = SelectSettingsVideoDeviceCapture(
        std::move(capabilities), user_media_request->VideoConstraints(),
        blink::MediaStreamVideoSource::kDefaultWidth,
        blink::MediaStreamVideoSource::kDefaultHeight,
        blink::MediaStreamVideoSource::kDefaultFrameRate);
    if (!settings.HasValue()) {
      String failed_constraint_name = String(settings.failed_constraint_name());
      MediaStreamRequestResult result =
          failed_constraint_name.IsEmpty()
              ? MediaStreamRequestResult::NO_HARDWARE
              : MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED;
      GetUserMediaRequestFailed(result, failed_constraint_name);
      return;
    }
    current_request_info_->stream_controls()->video.device_id =
        settings.device_id();
    current_request_info_->SetVideoCaptureSettings(
        settings, false /* is_content_capture */);
  
    if (current_request_info_->request()->Audio()) {
      absl::optional<base::UnguessableToken> audio_session_id =
          DetermineExistingAudioSessionId();
      GenerateStreamForCurrentRequestInfo(
          audio_session_id, audio_session_id.has_value()
                                ? StreamSelectionStrategy::SEARCH_BY_SESSION_ID
                                : StreamSelectionStrategy::FORCE_NEW_STREAM);
    } else {
      GenerateStreamForCurrentRequestInfo();
    }
  }
  
  void UserMediaProcessor::SelectVideoContentSettings() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    SendLogMessage(
        base::StringPrintf("SelectVideoContentSettings. request_id=%d.",
                           current_request_info_->request_id()));
    gfx::Size screen_size = GetScreenSize();
    blink::VideoCaptureSettings settings =
        blink::SelectSettingsVideoContentCapture(
            current_request_info_->request()->VideoConstraints(),
            current_request_info_->stream_controls()->video.stream_type,
            screen_size.width(), screen_size.height());
    if (!settings.HasValue()) {
      String failed_constraint_name = String(settings.failed_constraint_name());
      DCHECK(!failed_constraint_name.IsEmpty());
  
      GetUserMediaRequestFailed(
          MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED,
          failed_constraint_name);
      return;
    }
  
    const MediaStreamType stream_type =
        current_request_info_->stream_controls()->video.stream_type;
    if (stream_type != MediaStreamType::DISPLAY_VIDEO_CAPTURE &&
        stream_type != MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB) {
      current_request_info_->stream_controls()->video.device_id =
          settings.device_id();
    }
  
    current_request_info_->SetVideoCaptureSettings(settings,
                                                   true /* is_content_capture */);
    GenerateStreamForCurrentRequestInfo();
  }
  
  void UserMediaProcessor::GenerateStreamForCurrentRequestInfo(
      absl::optional<base::UnguessableToken> requested_audio_capture_session_id,
      blink::mojom::StreamSelectionStrategy strategy) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    SendLogMessage(base::StringPrintf(
        "GenerateStreamForCurrentRequestInfo({request_id=%d}, "
        "{audio.device_id=%s}, {video.device_id=%s})",
        current_request_info_->request_id(),
        current_request_info_->stream_controls()->audio.device_id.c_str(),
        current_request_info_->stream_controls()->video.device_id.c_str()));
    current_request_info_->set_state(RequestInfo::State::kSentForGeneration);
  
    // The browser replies to this request by invoking OnStreamGenerated().
    GetMediaStreamDispatcherHost()->GenerateStream(
        current_request_info_->request_id(),
        *current_request_info_->stream_controls(),
        current_request_info_->is_processing_user_gesture(),
        blink::mojom::blink::StreamSelectionInfo::New(
            strategy, requested_audio_capture_session_id),
        WTF::Bind(&UserMediaProcessor::OnStreamGenerated,
                  WrapWeakPersistent(this), current_request_info_->request_id()));
  }
  
  WebMediaStreamDeviceObserver*
  UserMediaProcessor::GetMediaStreamDeviceObserver() {
    auto* media_stream_device_observer =
        media_stream_device_observer_for_testing_;
    if (frame_) {  // Can be null for tests.
      auto* web_frame =
          static_cast<WebLocalFrame*>(WebFrame::FromCoreFrame(frame_));
      if (!web_frame || !web_frame->Client())
        return nullptr;
  
      // TODO(704136): Move ownership of |WebMediaStreamDeviceObserver| out of
      // RenderFrameImpl, back to UserMediaClient.
      media_stream_device_observer =
          web_frame->Client()->MediaStreamDeviceObserver();
      DCHECK(media_stream_device_observer);
    }
  
    return media_stream_device_observer;
  }
  
  void UserMediaProcessor::OnStreamGenerated(
      int request_id,
      MediaStreamRequestResult result,
      const String& label,
      const Vector<MediaStreamDevice>& audio_devices,
      const Vector<MediaStreamDevice>& video_devices,
      bool pan_tilt_zoom_allowed) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  
    if (result != MediaStreamRequestResult::OK) {
      OnStreamGenerationFailed(request_id, result);
      return;
    }
  
    if (!IsCurrentRequestInfo(request_id)) {
      // This can happen if the request is canceled or the frame reloads while
      // MediaStreamDispatcherHost is processing the request.
      SendLogMessage(base::StringPrintf(
          "OnStreamGenerated([request_id=%d]) => (ERROR: invalid request ID)",
          request_id));
      OnStreamGeneratedForCancelledRequest(audio_devices, video_devices);
      return;
    }
  
    current_request_info_->set_state(RequestInfo::State::kGenerated);
    current_request_info_->set_pan_tilt_zoom_allowed(pan_tilt_zoom_allowed);
  
    for (const auto* devices : {&audio_devices, &video_devices}) {
      for (const auto& device : *devices) {
        SendLogMessage(base::StringPrintf(
            "OnStreamGenerated({request_id=%d}, {label=%s}, {device=[id: %s, "
            "name: "
            "%s]})",
            request_id, label.Utf8().c_str(), device.id.c_str(),
            device.name.c_str()));
      }
    }
  
    current_request_info_->SetDevices(audio_devices, video_devices);
  
    if (video_devices.IsEmpty()) {
      StartTracks(label);
      return;
    }
  
    if (current_request_info_->is_video_content_capture()) {
      media::VideoCaptureFormat format =
          current_request_info_->video_capture_settings().Format();
      for (const auto& video_device : video_devices) {
        String video_device_id(video_device.id.data());
        current_request_info_->AddNativeVideoFormats(
            video_device_id,
            {media::VideoCaptureFormat(GetScreenSize(), format.frame_rate,
                                       format.pixel_format)});
      }
      StartTracks(label);
      return;
    }
  
    for (const auto& video_device : video_devices) {
      SendLogMessage(base::StringPrintf(
          "OnStreamGenerated({request_id=%d}, {label=%s}, {device=[id: %s, "
          "name: %s]}) => (Requesting video device formats)",
          request_id, label.Utf8().c_str(), video_device.id.c_str(),
          video_device.name.c_str()));
      String video_device_id(video_device.id.data());
      GetMediaDevicesDispatcher()->GetAllVideoInputDeviceFormats(
          video_device_id,
          WTF::Bind(&UserMediaProcessor::GotAllVideoInputFormatsForDevice,
                    WrapWeakPersistent(this),
                    WrapPersistent(current_request_info_->request()), label,
                    video_device_id));
    }
  }
  
  void UserMediaProcessor::GotAllVideoInputFormatsForDevice(
      UserMediaRequest* user_media_request,
      const String& label,
      const String& device_id,
      const Vector<media::VideoCaptureFormat>& formats) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    // The frame might reload or |user_media_request| might be cancelled while
    // video formats are queried. Do nothing if a different request is being
    // processed at this point.
    if (!IsCurrentRequestInfo(user_media_request))
      return;
  
    SendLogMessage(
        base::StringPrintf("GotAllVideoInputFormatsForDevice({request_id=%d}, "
                           "{label=%s}, {device=[id: %s]})",
                           current_request_info_->request_id(),
                           label.Utf8().c_str(), device_id.Utf8().c_str()));
    current_request_info_->AddNativeVideoFormats(device_id, formats);
    if (current_request_info_->CanStartTracks())
      StartTracks(label);
  }
  
  gfx::Size UserMediaProcessor::GetScreenSize() {
    gfx::Size screen_size(blink::kDefaultScreenCastWidth,
                          blink::kDefaultScreenCastHeight);
    if (frame_) {  // Can be null in tests.
      const display::ScreenInfo& info =
          frame_->GetChromeClient().GetScreenInfo(*frame_);
      screen_size = info.rect.size();
    }
    return screen_size;
  }
  
  void UserMediaProcessor::OnStreamGeneratedForCancelledRequest(
      const Vector<MediaStreamDevice>& audio_devices,
      const Vector<MediaStreamDevice>& video_devices) {
    SendLogMessage("OnStreamGeneratedForCancelledRequest()");
    // Only stop the device if the device is not used in another MediaStream.
    for (auto* it = audio_devices.begin(); it != audio_devices.end(); ++it) {
      if (!FindLocalSource(*it)) {
        String id(it->id.data());
        GetMediaStreamDispatcherHost()->StopStreamDevice(
            id, it->serializable_session_id());
      }
    }
  
    for (auto* it = video_devices.begin(); it != video_devices.end(); ++it) {
      if (!FindLocalSource(*it)) {
        String id(it->id.data());
        GetMediaStreamDispatcherHost()->StopStreamDevice(
            id, it->serializable_session_id());
      }
    }
  }
  
  // static
  void UserMediaProcessor::OnAudioSourceStartedOnAudioThread(
      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
      UserMediaProcessor* weak_ptr,
      blink::WebPlatformMediaStreamSource* source,
      MediaStreamRequestResult result,
      const blink::WebString& result_name) {
    PostCrossThreadTask(
        *task_runner.get(), FROM_HERE,
        CrossThreadBindOnce(&UserMediaProcessor::OnAudioSourceStarted,
                            WrapCrossThreadWeakPersistent(weak_ptr),
                            CrossThreadUnretained(source), result,
                            String(result_name)));
  }
  
  void UserMediaProcessor::OnAudioSourceStarted(
      blink::WebPlatformMediaStreamSource* source,
      MediaStreamRequestResult result,
      const String& result_name) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  
    for (auto* it = pending_local_sources_.begin();
         it != pending_local_sources_.end(); ++it) {
      blink::WebPlatformMediaStreamSource* const source_extra_data =
          (*it)->GetPlatformSource();
      if (source_extra_data != source)
        continue;
      if (result == MediaStreamRequestResult::OK)
        local_sources_.push_back((*it));
      pending_local_sources_.erase(it);
  
      NotifyCurrentRequestInfoOfAudioSourceStarted(source, result, result_name);
      return;
    }
  }
  
  void UserMediaProcessor::NotifyCurrentRequestInfoOfAudioSourceStarted(
      blink::WebPlatformMediaStreamSource* source,
      MediaStreamRequestResult result,
      const String& result_name) {
    // The only request possibly being processed is |current_request_info_|.
    if (current_request_info_)
      current_request_info_->OnAudioSourceStarted(source, result, result_name);
  }
  
  void UserMediaProcessor::OnStreamGenerationFailed(
      int request_id,
      MediaStreamRequestResult result) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    if (!IsCurrentRequestInfo(request_id)) {
      // This can happen if the request is canceled or the frame reloads while
      // MediaStreamDispatcherHost is processing the request.
      return;
    }
    SendLogMessage(base::StringPrintf("OnStreamGenerationFailed({request_id=%d})",
                                      current_request_info_->request_id()));
  
    GetUserMediaRequestFailed(result);
    DeleteUserMediaRequest(current_request_info_->request());
  }
  
  void UserMediaProcessor::OnDeviceStopped(const MediaStreamDevice& device) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(base::StringPrintf(
        "OnDeviceStopped({session_id=%s}, {device_id=%s})",
        device.session_id().ToString().c_str(), device.id.c_str()));
  
    MediaStreamSource* source = FindLocalSource(device);
    if (!source) {
      // This happens if the same device is used in several guM requests or
      // if a user happens to stop a track from JS at the same time
      // as the underlying media device is unplugged from the system.
      return;
    }
  
    StopLocalSource(source, false);
    RemoveLocalSource(source);
  }
  
  void UserMediaProcessor::OnDeviceChanged(const MediaStreamDevice& old_device,
                                           const MediaStreamDevice& new_device) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    // TODO(https://crbug.com/1017219): possibly useful in native logs as well.
    DVLOG(1) << "UserMediaProcessor::OnDeviceChange("
             << "{old_device_id = " << old_device.id
             << ", session id = " << old_device.session_id()
             << ", type = " << old_device.type << "}"
             << "{new_device_id = " << new_device.id
             << ", session id = " << new_device.session_id()
             << ", type = " << new_device.type << "})";
  
    MediaStreamSource* source = FindLocalSource(old_device);
    if (!source) {
      // This happens if the same device is used in several guM requests or
      // if a user happens to stop a track from JS at the same time
      // as the underlying media device is unplugged from the system.
      DVLOG(1) << "failed to find existing source with device " << old_device.id;
      return;
    }
  
    if (old_device.type != MediaStreamType::NO_SERVICE &&
        new_device.type == MediaStreamType::NO_SERVICE) {
      // At present, this will only happen to the case that a new desktop capture
      // source without audio share is selected, then the previous audio capture
      // device should be stopped if existing.
      DCHECK(blink::IsAudioInputMediaType(old_device.type));
      OnDeviceStopped(old_device);
      return;
    }
  
    WebPlatformMediaStreamSource* const source_impl = source->GetPlatformSource();
    source_impl->ChangeSource(new_device);
  }
  
  void UserMediaProcessor::OnDeviceRequestStateChange(
      const MediaStreamDevice& device,
      const mojom::blink::MediaStreamStateChange new_state) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(base::StringPrintf(
        "OnDeviceRequestStateChange({session_id=%s}, {device_id=%s}, "
        "{new_state=%s})",
        device.session_id().ToString().c_str(), device.id.c_str(),
        (new_state == mojom::blink::MediaStreamStateChange::PAUSE ? "PAUSE"
                                                                  : "PLAY")));
  
    MediaStreamSource* source = FindLocalSource(device);
    if (!source) {
      // This happens if the same device is used in several guM requests or
      // if a user happens to stop a track from JS at the same time
      // as the underlying media device is unplugged from the system.
      return;
    }
  
    WebPlatformMediaStreamSource* const source_impl = source->GetPlatformSource();
    source_impl->SetSourceMuted(new_state ==
                                mojom::blink::MediaStreamStateChange::PAUSE);
    MediaStreamVideoSource* video_source =
        static_cast<blink::MediaStreamVideoSource*>(source_impl);
    if (!video_source) {
      return;
    }
    if (new_state == mojom::blink::MediaStreamStateChange::PAUSE) {
      if (video_source->IsRunning()) {
        video_source->StopForRestart(base::DoNothing(),
                                     /*send_black_frame=*/true);
      }
    } else if (new_state == mojom::blink::MediaStreamStateChange::PLAY) {
      if (video_source->IsStoppedForRestart()) {
        video_source->Restart(*video_source->GetCurrentFormat(),
                              base::DoNothing());
      }
    } else {
      NOTREACHED();
    }
  }
  
  void UserMediaProcessor::OnDeviceCaptureHandleChange(
      const MediaStreamDevice& device) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(base::StringPrintf(
        "OnDeviceCaptureHandleChange({session_id=%s}, {device_id=%s})",
        device.session_id().ToString().c_str(), device.id.c_str()));
  
    MediaStreamSource* const source = FindLocalSource(device);
    if (!source) {
      // This happens if the same device is used in several guM requests or
      // if a user happens to stop a track from JS at the same time
      // as the underlying media device is unplugged from the system.
      return;
    }
  
    source->OnDeviceCaptureHandleChange(device);
  }
  
  void UserMediaProcessor::Trace(Visitor* visitor) const {
    visitor->Trace(dispatcher_host_);
    visitor->Trace(frame_);
    visitor->Trace(current_request_info_);
    visitor->Trace(local_sources_);
    visitor->Trace(pending_local_sources_);
  }
  
  MediaStreamSource* UserMediaProcessor::InitializeVideoSourceObject(
      const MediaStreamDevice& device) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    SendLogMessage(base::StringPrintf(
        "UMP::InitializeVideoSourceObject({request_id=%d}, {device=[id: %s, "
        "name: %s]})",
        current_request_info_->request_id(), device.id.c_str(),
        device.name.c_str()));
    MediaStreamSource* source = FindOrInitializeSourceObject(device);
    if (!source->GetPlatformSource()) {
      auto video_source = CreateVideoSource(
          device, WTF::Bind(&UserMediaProcessor::OnLocalSourceStopped,
                            WrapWeakPersistent(this)));
      source->SetPlatformSource(std::move(video_source));
  
      String device_id(device.id.data());
      source->SetCapabilities(ComputeCapabilitiesForVideoSource(
          // TODO(crbug.com/704136): Change ComputeCapabilitiesForVideoSource to
          // operate over WTF::Vector.
          String::FromUTF8(device.id),
          ToStdVector(*current_request_info_->GetNativeVideoFormats(device_id)),
          static_cast<mojom::blink::FacingMode>(device.video_facing),
          current_request_info_->is_video_device_capture(), device.group_id));
      local_sources_.push_back(source);
    }
    return source;
  }
  
  MediaStreamSource* UserMediaProcessor::InitializeAudioSourceObject(
      const MediaStreamDevice& device,
      bool* is_pending) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    SendLogMessage(
        base::StringPrintf("InitializeAudioSourceObject({session_id=%s})",
                           device.session_id().ToString().c_str()));
  
    *is_pending = true;
  
    // See if the source is already being initialized.
    auto* pending = FindPendingLocalSource(device);
    if (pending)
      return pending;
  
    MediaStreamSource* source = FindOrInitializeSourceObject(device);
    if (source->GetPlatformSource()) {
      // The only return point for non-pending sources.
      *is_pending = false;
      return source;
    }
  
    // While sources are being initialized, keep them in a separate array.
    // Once they've finished initialized, they'll be moved over to local_sources_.
    // See OnAudioSourceStarted for more details.
    pending_local_sources_.push_back(source);
  
    blink::WebPlatformMediaStreamSource::ConstraintsRepeatingCallback
        source_ready = ConvertToBaseRepeatingCallback(CrossThreadBindRepeating(
            &UserMediaProcessor::OnAudioSourceStartedOnAudioThread, task_runner_,
            WrapCrossThreadWeakPersistent(this)));
  
    std::unique_ptr<blink::MediaStreamAudioSource> audio_source =
        CreateAudioSource(device, std::move(source_ready));
    audio_source->SetStopCallback(WTF::Bind(
        &UserMediaProcessor::OnLocalSourceStopped, WrapWeakPersistent(this)));
  
  #if DCHECK_IS_ON()
    for (auto local_source : local_sources_) {
      auto* platform_source = static_cast<WebPlatformMediaStreamSource*>(
          local_source->GetPlatformSource());
      DCHECK(platform_source);
      if (platform_source->device().id == audio_source->device().id) {
        auto* audio_platform_source =
            static_cast<MediaStreamAudioSource*>(platform_source);
        auto* processed_existing_source =
            ProcessedLocalAudioSource::From(audio_platform_source);
        auto* processed_new_source =
            ProcessedLocalAudioSource::From(audio_source.get());
        if (processed_new_source && processed_existing_source) {
          DCHECK(audio_source->HasSameNonReconfigurableSettings(
              audio_platform_source));
        }
      }
    }
  #endif  // DCHECK_IS_ON()
  
    MediaStreamSource::Capabilities capabilities;
    capabilities.echo_cancellation = {true, false};
    capabilities.echo_cancellation_type.ReserveCapacity(3);
    capabilities.echo_cancellation_type.emplace_back(
        String::FromUTF8(kEchoCancellationTypeBrowser));
    capabilities.echo_cancellation_type.emplace_back(
        String::FromUTF8(kEchoCancellationTypeAec3));
    if (device.input.effects() &
        (media::AudioParameters::ECHO_CANCELLER |
         media::AudioParameters::EXPERIMENTAL_ECHO_CANCELLER)) {
      capabilities.echo_cancellation_type.emplace_back(
          String::FromUTF8(kEchoCancellationTypeSystem));
    }
    capabilities.auto_gain_control = {true, false};
    capabilities.noise_suppression = {true, false};
    capabilities.sample_size = {
        media::SampleFormatToBitsPerChannel(media::kSampleFormatS16),  // min
        media::SampleFormatToBitsPerChannel(media::kSampleFormatS16)   // max
    };
    auto device_parameters = audio_source->device().input;
    if (device_parameters.IsValid()) {
      capabilities.channel_count = {1, device_parameters.channels()};
      capabilities.sample_rate = {std::min(media::kAudioProcessingSampleRateHz,
                                           device_parameters.sample_rate()),
                                  std::max(media::kAudioProcessingSampleRateHz,
                                           device_parameters.sample_rate())};
      double fallback_latency =
          static_cast<double>(blink::kFallbackAudioLatencyMs) / 1000;
      double min_latency, max_latency;
      std::tie(min_latency, max_latency) =
          blink::GetMinMaxLatenciesForAudioParameters(device_parameters);
      capabilities.latency = {std::min(fallback_latency, min_latency),
                              std::max(fallback_latency, max_latency)};
    }
  
    capabilities.device_id = blink::WebString::FromUTF8(device.id);
    if (device.group_id)
      capabilities.group_id = blink::WebString::FromUTF8(*device.group_id);
  
    source->SetPlatformSource(std::move(audio_source));
    source->SetCapabilities(capabilities);
    return source;
  }
  
  std::unique_ptr<blink::MediaStreamAudioSource>
  UserMediaProcessor::CreateAudioSource(
      const MediaStreamDevice& device,
      blink::WebPlatformMediaStreamSource::ConstraintsRepeatingCallback
          source_ready) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
  
    StreamControls* stream_controls = current_request_info_->stream_controls();
    // If the audio device is a loopback device (for screen capture), or if the
    // constraints/effects parameters indicate no audio processing is needed,
    // create an efficient, direct-path MediaStreamAudioSource instance.
    blink::AudioProcessingProperties audio_processing_properties =
        current_request_info_->audio_capture_settings()
            .audio_processing_properties();
    if (blink::IsScreenCaptureMediaType(device.type) ||
        !blink::MediaStreamAudioProcessor::WouldModifyAudio(
            audio_processing_properties)) {
      SendLogMessage(
          base::StringPrintf("%s => (no audiprocessing is used)", __func__));
      return std::make_unique<blink::LocalMediaStreamAudioSource>(
          frame_, device,
          base::OptionalOrNullptr(current_request_info_->audio_capture_settings()
                                      .requested_buffer_size()),
          stream_controls->disable_local_echo, std::move(source_ready),
          task_runner_);
    }
  
    // The audio device is not associated with screen capture and also requires
    // processing.
    SendLogMessage(
        base::StringPrintf("%s => (audiprocessing is required)", __func__));
    return std::make_unique<blink::ProcessedLocalAudioSource>(
        *frame_, device, stream_controls->disable_local_echo,
        audio_processing_properties,
        current_request_info_->audio_capture_settings().num_channels(),
        std::move(source_ready), task_runner_);
  }
  
  std::unique_ptr<blink::MediaStreamVideoSource>
  UserMediaProcessor::CreateVideoSource(
      const MediaStreamDevice& device,
      blink::WebPlatformMediaStreamSource::SourceStoppedCallback stop_callback) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    DCHECK(current_request_info_->video_capture_settings().HasValue());
  
    return std::make_unique<blink::MediaStreamVideoCapturerSource>(
        frame_, std::move(stop_callback), device,
        current_request_info_->video_capture_settings().capture_params(),
        WTF::BindRepeating(&blink::LocalVideoCapturerSource::Create,
                           frame_->GetTaskRunner(blink::TaskType::kInternalMedia),
                           WrapWeakPersistent(frame_.Get())));
  }
  
  void UserMediaProcessor::StartTracks(const String& label) {
    DCHECK(current_request_info_->request());
    SendLogMessage(base::StringPrintf("StartTracks({request_id=%d}, {label=%s})",
                                      current_request_info_->request_id(),
                                      label.Utf8().c_str()));
    if (auto* media_stream_device_observer = GetMediaStreamDeviceObserver()) {
      media_stream_device_observer->AddStream(
          blink::WebString(label),
          ToStdVector(current_request_info_->audio_devices()),
          ToStdVector(current_request_info_->video_devices()),
          WTF::BindRepeating(&UserMediaProcessor::OnDeviceStopped,
                             WrapWeakPersistent(this)),
          WTF::BindRepeating(&UserMediaProcessor::OnDeviceChanged,
                             WrapWeakPersistent(this)),
          WTF::BindRepeating(&UserMediaProcessor::OnDeviceRequestStateChange,
                             WrapWeakPersistent(this)),
          WTF::BindRepeating(&UserMediaProcessor::OnDeviceCaptureHandleChange,
                             WrapWeakPersistent(this)));
    }
  
    HeapVector<Member<MediaStreamComponent>> audio_tracks(
        current_request_info_->audio_devices().size());
    CreateAudioTracks(current_request_info_->audio_devices(), &audio_tracks);
  
    HeapVector<Member<MediaStreamComponent>> video_tracks(
        current_request_info_->video_devices().size());
    CreateVideoTracks(current_request_info_->video_devices(), &video_tracks);
  
    String blink_id = label;
    current_request_info_->InitializeWebStream(blink_id, audio_tracks,
                                               video_tracks);
  
    // Wait for the tracks to be started successfully or to fail.
    current_request_info_->CallbackOnTracksStarted(
        WTF::Bind(&UserMediaProcessor::OnCreateNativeTracksCompleted,
                  WrapWeakPersistent(this), label));
  }
  
  void UserMediaProcessor::CreateVideoTracks(
      const Vector<MediaStreamDevice>& devices,
      HeapVector<Member<MediaStreamComponent>>* components) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    DCHECK_EQ(devices.size(), components->size());
    SendLogMessage(base::StringPrintf("UMP::CreateVideoTracks({request_id=%d})",
                                      current_request_info_->request_id()));
  
    for (WTF::wtf_size_t i = 0; i < devices.size(); ++i) {
      MediaStreamSource* source = InitializeVideoSourceObject(devices[i]);
      (*components)[i] = current_request_info_->CreateAndStartVideoTrack(source);
    }
  }
  
  void UserMediaProcessor::CreateAudioTracks(
      const Vector<MediaStreamDevice>& devices,
      HeapVector<Member<MediaStreamComponent>>* components) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(current_request_info_);
    DCHECK_EQ(devices.size(), components->size());
  
    Vector<MediaStreamDevice> overridden_audio_devices = devices;
    bool render_to_associated_sink =
        current_request_info_->audio_capture_settings().HasValue() &&
        current_request_info_->audio_capture_settings()
            .render_to_associated_sink();
    SendLogMessage(
        base::StringPrintf("CreateAudioTracks({render_to_associated_sink=%d})",
                           render_to_associated_sink));
    if (!render_to_associated_sink) {
      // If the GetUserMedia request did not explicitly set the constraint
      // kMediaStreamRenderToAssociatedSink, the output device id must
      // be removed.
      for (auto& device : overridden_audio_devices)
        device.matched_output_device_id.reset();
    }
  
    for (WTF::wtf_size_t i = 0; i < overridden_audio_devices.size(); ++i) {
      bool is_pending = false;
      MediaStreamSource* source =
          InitializeAudioSourceObject(overridden_audio_devices[i], &is_pending);
      (*components)[i] = MakeGarbageCollected<MediaStreamComponent>(source);
      current_request_info_->StartAudioTrack((*components)[i], is_pending);
      // At this point the source has started, and its audio parameters have been
      // set. Thus, all audio processing properties are known and can be surfaced
      // to |source|.
      SurfaceAudioProcessingSettings(source);
    }
  }
  
  void UserMediaProcessor::OnCreateNativeTracksCompleted(
      const String& label,
      RequestInfo* request_info,
      MediaStreamRequestResult result,
      const String& constraint_name) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(base::StringPrintf(
        "UMP::OnCreateNativeTracksCompleted({request_id = %d}, {label=%s})",
        request_info->request_id(), label.Utf8().c_str()));
    if (result == MediaStreamRequestResult::OK) {
      GetUserMediaRequestSucceeded(request_info->descriptor(),
                                   request_info->request());
      GetMediaStreamDispatcherHost()->OnStreamStarted(label);
    } else {
      GetUserMediaRequestFailed(result, constraint_name);
  
      for (auto web_track : request_info->descriptor()->AudioComponents()) {
        MediaStreamTrackPlatform* track =
            MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(web_track));
        if (track)
          track->Stop();
      }
  
      for (auto web_track : request_info->descriptor()->VideoComponents()) {
        MediaStreamTrackPlatform* track =
            MediaStreamTrackPlatform::GetTrack(WebMediaStreamTrack(web_track));
        if (track)
          track->Stop();
      }
    }
  
    DeleteUserMediaRequest(request_info->request());
  }
  
  void UserMediaProcessor::GetUserMediaRequestSucceeded(
      MediaStreamDescriptor* descriptor,
      UserMediaRequest* user_media_request) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    DCHECK(IsCurrentRequestInfo(user_media_request));
    SendLogMessage(
        base::StringPrintf("GetUserMediaRequestSucceeded({request_id=%d})",
                           current_request_info_->request_id()));
  
    // Completing the getUserMedia request can lead to that the RenderFrame and
    // the UserMediaClient/UserMediaProcessor are destroyed if the JavaScript
    // code request the frame to be destroyed within the scope of the callback.
    // Therefore, post a task to complete the request with a clean stack.
    task_runner_->PostTask(
        FROM_HERE,
        WTF::Bind(&UserMediaProcessor::DelayedGetUserMediaRequestSucceeded,
                  WrapWeakPersistent(this), current_request_info_->request_id(),
                  WrapPersistent(descriptor),
                  WrapPersistent(user_media_request)));
  }
  
  void UserMediaProcessor::DelayedGetUserMediaRequestSucceeded(
      int request_id,
      MediaStreamDescriptor* component,
      UserMediaRequest* user_media_request) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(base::StringPrintf(
        "DelayedGetUserMediaRequestSucceeded({request_id=%d}, {result=%s})",
        request_id,
        MediaStreamRequestResultToString(MediaStreamRequestResult::OK)));
    blink::LogUserMediaRequestResult(MediaStreamRequestResult::OK);
    DeleteUserMediaRequest(user_media_request);
    user_media_request->Succeed(component);
  }
  
  void UserMediaProcessor::GetUserMediaRequestFailed(
      MediaStreamRequestResult result,
      const String& constraint_name) {
    DCHECK(current_request_info_);
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(
        base::StringPrintf("GetUserMediaRequestFailed({request_id=%d})",
                           current_request_info_->request_id()));
  
    // Completing the getUserMedia request can lead to that the RenderFrame and
    // the UserMediaClient/UserMediaProcessor are destroyed if the JavaScript
    // code request the frame to be destroyed within the scope of the callback.
    // Therefore, post a task to complete the request with a clean stack.
    task_runner_->PostTask(
        FROM_HERE,
        WTF::Bind(&UserMediaProcessor::DelayedGetUserMediaRequestFailed,
                  WrapWeakPersistent(this), current_request_info_->request_id(),
                  WrapPersistent(current_request_info_->request()), result,
                  constraint_name));
  }
  
  void UserMediaProcessor::DelayedGetUserMediaRequestFailed(
      int request_id,
      UserMediaRequest* user_media_request,
      MediaStreamRequestResult result,
      const String& constraint_name) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    blink::LogUserMediaRequestResult(result);
    SendLogMessage(base::StringPrintf(
        "DelayedGetUserMediaRequestFailed({request_id=%d}, {result=%s})",
        request_id, MediaStreamRequestResultToString(result)));
    DeleteUserMediaRequest(user_media_request);
    switch (result) {
      case MediaStreamRequestResult::OK:
      case MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS:
        NOTREACHED();
        return;
      case MediaStreamRequestResult::PERMISSION_DENIED:
        user_media_request->Fail(UserMediaRequest::Error::kPermissionDenied,
                                 "Permission denied");
        return;
      case MediaStreamRequestResult::PERMISSION_DISMISSED:
        user_media_request->Fail(UserMediaRequest::Error::kPermissionDismissed,
                                 "Permission dismissed");
        return;
      case MediaStreamRequestResult::INVALID_STATE:
        user_media_request->Fail(UserMediaRequest::Error::kInvalidState,
                                 "Invalid state");
        return;
      case MediaStreamRequestResult::NO_HARDWARE:
        user_media_request->Fail(UserMediaRequest::Error::kDevicesNotFound,
                                 "Requested device not found");
        return;
      case MediaStreamRequestResult::INVALID_SECURITY_ORIGIN:
        user_media_request->Fail(UserMediaRequest::Error::kSecurityError,
                                 "Invalid security origin");
        return;
      case MediaStreamRequestResult::TAB_CAPTURE_FAILURE:
        user_media_request->Fail(UserMediaRequest::Error::kTabCapture,
                                 "Error starting tab capture");
        return;
      case MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE:
        user_media_request->Fail(UserMediaRequest::Error::kScreenCapture,
                                 "Error starting screen capture");
        return;
      case MediaStreamRequestResult::CAPTURE_FAILURE:
        user_media_request->Fail(UserMediaRequest::Error::kCapture,
                                 "Error starting capture");
        return;
      case MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED:
        user_media_request->FailConstraint(constraint_name, "");
        return;
      case MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO:
        user_media_request->Fail(UserMediaRequest::Error::kTrackStart,
                                 "Could not start audio source");
        return;
      case MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO:
        user_media_request->Fail(UserMediaRequest::Error::kTrackStart,
                                 "Could not start video source");
        return;
      case MediaStreamRequestResult::NOT_SUPPORTED:
        user_media_request->Fail(UserMediaRequest::Error::kNotSupported,
                                 "Not supported");
        return;
      case MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN:
        user_media_request->Fail(UserMediaRequest::Error::kFailedDueToShutdown,
                                 "Failed due to shutdown");
        return;
      case MediaStreamRequestResult::KILL_SWITCH_ON:
        user_media_request->Fail(UserMediaRequest::Error::kKillSwitchOn, "");
        return;
      case MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED:
        user_media_request->Fail(UserMediaRequest::Error::kSystemPermissionDenied,
                                 "Permission denied by system");
        return;
      case MediaStreamRequestResult::DEVICE_IN_USE:
        user_media_request->Fail(UserMediaRequest::Error::kDeviceInUse,
                                 "Device in use");
        return;
    }
    NOTREACHED();
    user_media_request->Fail(UserMediaRequest::Error::kPermissionDenied, "");
  }
  
  MediaStreamSource* UserMediaProcessor::FindLocalSource(
      const LocalStreamSources& sources,
      const MediaStreamDevice& device) const {
    for (auto local_source : sources) {
      WebPlatformMediaStreamSource* const source =
          local_source->GetPlatformSource();
      const MediaStreamDevice& active_device = source->device();
      if (IsSameDevice(active_device, device))
        return local_source;
    }
    return nullptr;
  }
  
  MediaStreamSource* UserMediaProcessor::FindOrInitializeSourceObject(
      const MediaStreamDevice& device) {
    MediaStreamSource* existing_source = FindLocalSource(device);
    if (existing_source) {
      DVLOG(1) << "Source already exists. Reusing source with id "
               << existing_source->Id().Utf8();
      return existing_source;
    }
  
    MediaStreamSource::StreamType type = IsAudioInputMediaType(device.type)
                                             ? MediaStreamSource::kTypeAudio
                                             : MediaStreamSource::kTypeVideo;
  
    auto* source = MakeGarbageCollected<MediaStreamSource>(
        String::FromUTF8(device.id), type, String::FromUTF8(device.name),
        false /* remote */);
    if (device.group_id)
      source->SetGroupId(String::FromUTF8(*device.group_id));
    return source;
  }
  
  bool UserMediaProcessor::RemoveLocalSource(MediaStreamSource* source) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    SendLogMessage(base::StringPrintf(
        "RemoveLocalSource({id=%s}, {name=%s}, {group_id=%s})",
        source->Id().Utf8().c_str(), source->GetName().Utf8().c_str(),
        source->GroupId().Utf8().c_str()));
  
    for (auto* device_it = local_sources_.begin();
         device_it != local_sources_.end(); ++device_it) {
      if (IsSameSource(*device_it, source)) {
        local_sources_.erase(device_it);
        return true;
      }
    }
  
    // Check if the source was pending.
    for (auto* device_it = pending_local_sources_.begin();
         device_it != pending_local_sources_.end(); ++device_it) {
      if (IsSameSource(*device_it, source)) {
        WebPlatformMediaStreamSource* const platform_source =
            source->GetPlatformSource();
        MediaStreamRequestResult result;
        String message;
        if (source->GetType() == MediaStreamSource::kTypeAudio) {
          auto error = MediaStreamAudioSource::From(source)->ErrorCode();
          switch (error.value_or(AudioSourceErrorCode::kUnknown)) {
            case AudioSourceErrorCode::kSystemPermissions:
              result = MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
              message =
                  "System Permssions prevented access to audio capture device";
              break;
            case AudioSourceErrorCode::kDeviceInUse:
              result = MediaStreamRequestResult::DEVICE_IN_USE;
              message = "Audio capture device already in use";
              break;
            default:
              result = MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO;
              message = "Failed to access audio capture device";
          }
        } else {
          result = MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO;
          message = "Failed to access video capture device";
        }
        NotifyCurrentRequestInfoOfAudioSourceStarted(platform_source, result,
                                                     message);
        pending_local_sources_.erase(device_it);
        return true;
      }
    }
  
    return false;
  }
  
  bool UserMediaProcessor::IsCurrentRequestInfo(int request_id) const {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    return current_request_info_ &&
           current_request_info_->request_id() == request_id;
  }
  
  bool UserMediaProcessor::IsCurrentRequestInfo(
      UserMediaRequest* user_media_request) const {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    return current_request_info_ &&
           current_request_info_->request() == user_media_request;
  }
  
  bool UserMediaProcessor::DeleteUserMediaRequest(
      UserMediaRequest* user_media_request) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    if (current_request_info_ &&
        current_request_info_->request() == user_media_request) {
      current_request_info_ = nullptr;
      std::move(request_completed_cb_).Run();
      return true;
    }
    return false;
  }
  
  void UserMediaProcessor::StopAllProcessing() {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    if (current_request_info_) {
      switch (current_request_info_->state()) {
        case RequestInfo::State::kSentForGeneration:
          // Let the browser process know that the previously sent request must be
          // canceled.
          GetMediaStreamDispatcherHost()->CancelRequest(
              current_request_info_->request_id());
          FALLTHROUGH;
  
        case RequestInfo::State::kNotSentForGeneration:
          LogUserMediaRequestWithNoResult(
              blink::MEDIA_STREAM_REQUEST_NOT_GENERATED);
          break;
  
        case RequestInfo::State::kGenerated:
          LogUserMediaRequestWithNoResult(
              blink::MEDIA_STREAM_REQUEST_PENDING_MEDIA_TRACKS);
          break;
      }
      current_request_info_ = nullptr;
    }
    request_completed_cb_.Reset();
  
    // Loop through all current local sources and stop the sources.
    auto* it = local_sources_.begin();
    while (it != local_sources_.end()) {
      StopLocalSource(*it, true);
      it = local_sources_.erase(it);
    }
  }
  
  void UserMediaProcessor::OnLocalSourceStopped(
      const blink::WebMediaStreamSource& source) {
    // The client can be null if the frame is already detached.
    // If it's already detached, dispatcher_host_ shouldn't be bound again.
    // (ref: crbug.com/1105842)
    if (!frame_->Client())
      return;
  
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    blink::WebPlatformMediaStreamSource* source_impl = source.GetPlatformSource();
    SendLogMessage(base::StringPrintf(
        "OnLocalSourceStopped({session_id=%s})",
        source_impl->device().session_id().ToString().c_str()));
  
    const bool some_source_removed = RemoveLocalSource(source);
    CHECK(some_source_removed);
  
    if (auto* media_stream_device_observer = GetMediaStreamDeviceObserver())
      media_stream_device_observer->RemoveStreamDevice(source_impl->device());
  
    String device_id(source_impl->device().id.data());
    GetMediaStreamDispatcherHost()->StopStreamDevice(
        device_id, source_impl->device().serializable_session_id());
  }
  
  void UserMediaProcessor::StopLocalSource(MediaStreamSource* source,
                                           bool notify_dispatcher) {
    WebPlatformMediaStreamSource* source_impl = source->GetPlatformSource();
    SendLogMessage(base::StringPrintf(
        "StopLocalSource({session_id=%s})",
        source_impl->device().session_id().ToString().c_str()));
  
    if (notify_dispatcher) {
      if (auto* media_stream_device_observer = GetMediaStreamDeviceObserver())
        media_stream_device_observer->RemoveStreamDevice(source_impl->device());
  
      String device_id(source_impl->device().id.data());
      GetMediaStreamDispatcherHost()->StopStreamDevice(
          device_id, source_impl->device().serializable_session_id());
    }
  
    source_impl->ResetSourceStoppedCallback();
    source_impl->StopSource();
  }
  
  bool UserMediaProcessor::HasActiveSources() const {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    return !local_sources_.IsEmpty();
  }
  
  #if !defined(OS_ANDROID)
  void UserMediaProcessor::FocusCapturedSurface(const String& label, bool focus) {
    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
    GetMediaStreamDispatcherHost()->FocusCapturedSurface(label, focus);
  }
  #endif
  
  blink::mojom::blink::MediaStreamDispatcherHost*
  UserMediaProcessor::GetMediaStreamDispatcherHost() {
    if (!dispatcher_host_.is_bound()) {
      frame_->GetBrowserInterfaceBroker().GetInterface(
          dispatcher_host_.BindNewPipeAndPassReceiver(task_runner_));
    }
    return dispatcher_host_.get();
  }
  
  blink::mojom::blink::MediaDevicesDispatcherHost*
  UserMediaProcessor::GetMediaDevicesDispatcher() {
    return media_devices_dispatcher_cb_.Run();
  }
  
  const blink::AudioCaptureSettings&
  UserMediaProcessor::AudioCaptureSettingsForTesting() const {
    DCHECK(current_request_info_);
    return current_request_info_->audio_capture_settings();
  }
  
  const blink::VideoCaptureSettings&
  UserMediaProcessor::VideoCaptureSettingsForTesting() const {
    DCHECK(current_request_info_);
    return current_request_info_->video_capture_settings();
  }
  
  void UserMediaProcessor::SetMediaStreamDeviceObserverForTesting(
      WebMediaStreamDeviceObserver* media_stream_device_observer) {
    DCHECK(!GetMediaStreamDeviceObserver());
    DCHECK(media_stream_device_observer);
    media_stream_device_observer_for_testing_ = media_stream_device_observer;
  }
  
  }  // namespace blink
  