blob: f644df8a8dd83dc170c7e02ade6cb98762d2d814 [file] [log] [blame]
// 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 <cctype>
#include <list>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/rand_util.h"
#include "base/ranges/algorithm.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/threading/thread.h"
#include "base/threading/thread_local.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/browser/media/capture/desktop_capture_device_uma_types.h"
#include "content/browser/media/media_devices_permission_checker.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/renderer_host/media/audio_input_device_manager.h"
#include "content/browser/renderer_host/media/audio_service_listener.h"
#include "content/browser/renderer_host/media/in_process_video_capture_provider.h"
#include "content/browser/renderer_host/media/media_capture_devices_impl.h"
#include "content/browser/renderer_host/media/media_devices_manager.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/browser/renderer_host/media/service_video_capture_provider.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/browser/renderer_host/media/video_capture_provider_switcher.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/browser/desktop_streams_registry.h"
#include "content/public/browser/media_observer.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents_media_capture_id.h"
#include "content/public/common/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/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_ASH)
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "content/browser/gpu/chromeos/video_capture_dependencies.h"
#include "content/browser/gpu/gpu_memory_buffer_manager_singleton.h"
#include "content/public/browser/chromeos/multi_capture_service.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
#include "media/capture/video/chromeos/public/cros_features.h"
#include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/lacros/lacros_service.h"
#endif
using blink::mojom::MediaDeviceType;
namespace content {
base::LazyInstance<base::ThreadLocalPointer<MediaStreamManager>>::Leaky
g_media_stream_manager_tls_ptr = LAZY_INSTANCE_INITIALIZER;
using blink::MediaStreamDevice;
using blink::MediaStreamDevices;
using blink::MediaStreamRequestType;
using blink::StreamControls;
using blink::TrackControls;
using blink::mojom::GetOpenDeviceResponse;
using blink::mojom::MediaStreamRequestResult;
using blink::mojom::MediaStreamType;
using blink::mojom::StreamSelectionInfo;
using blink::mojom::StreamSelectionInfoPtr;
using blink::mojom::StreamSelectionStrategy;
namespace {
// Creates a random label used to identify requests.
std::string RandomLabel() {
// An earlier PeerConnection spec [1] defined MediaStream::label alphabet as
// an uuid with characters from range: U+0021, U+0023 to U+0027, U+002A to
// U+002B, U+002D to U+002E, U+0030 to U+0039, U+0041 to U+005A, U+005E to
// U+007E. That causes problems with searching for labels in bots, so we use a
// safe alphanumeric subset |kAlphabet|.
// [1] http://dev.w3.org/2011/webrtc/editor/webrtc.html
static const char kAlphabet[] =
"0123456789"
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const size_t kRfc4122LengthLabel = 36u;
std::string label(kRfc4122LengthLabel, ' ');
for (char& c : label) {
// Use |std::size(kAlphabet) - 1| to avoid |kAlphabet|s terminating '\0';
c = kAlphabet[base::RandGenerator(std::size(kAlphabet) - 1)];
DCHECK(std::isalnum(c)) << c;
}
return label;
}
// Turns off available audio effects (removes the flag) if the options
// explicitly turn them off.
void FilterAudioEffects(const StreamControls& controls, int* effects) {
DCHECK(effects);
// TODO(ajm): Should we handle ECHO_CANCELLER here?
}
// Unlike other effects, hotword is off by default, so turn it on if it's
// requested and available.
void EnableHotwordEffect(const StreamControls& controls, int* effects) {
DCHECK(effects);
if (controls.hotword_enabled) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Only enable if a hotword device exists.
if (ash::CrasAudioHandler::Get()->HasHotwordDevice())
*effects |= media::AudioParameters::HOTWORD;
#endif
}
}
// Gets raw |device_id| and |group_id| when given a hashed device_id
// |hmac_device_id|. Both |device_id| and |group_id| could be null pointers.
bool GetDeviceIDAndGroupIDFromHMAC(
const std::string& salt,
const url::Origin& security_origin,
const std::string& hmac_device_id,
const blink::WebMediaDeviceInfoArray& devices,
std::string* device_id,
absl::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 (!MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
salt, security_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()
? absl::nullopt
: absl::make_optional<std::string>(device_info.group_id);
}
return true;
}
return false;
}
MediaStreamType ConvertToMediaStreamType(MediaDeviceType type) {
switch (type) {
case MediaDeviceType::MEDIA_AUDIO_INPUT:
return MediaStreamType::DEVICE_AUDIO_CAPTURE;
case MediaDeviceType::MEDIA_VIDEO_INPUT:
return MediaStreamType::DEVICE_VIDEO_CAPTURE;
default:
NOTREACHED();
}
return MediaStreamType::NO_SERVICE;
}
MediaDeviceType ConvertToMediaDeviceType(MediaStreamType stream_type) {
switch (stream_type) {
case MediaStreamType::DEVICE_AUDIO_CAPTURE:
return MediaDeviceType::MEDIA_AUDIO_INPUT;
case MediaStreamType::DEVICE_VIDEO_CAPTURE:
return MediaDeviceType::MEDIA_VIDEO_INPUT;
default:
NOTREACHED();
}
return MediaDeviceType::NUM_MEDIA_DEVICE_TYPES;
}
const char* DeviceTypeToString(MediaDeviceType type) {
switch (type) {
case MediaDeviceType::MEDIA_AUDIO_INPUT:
return "DEVICE_AUDIO_INPUT";
case MediaDeviceType::MEDIA_AUDIO_OUTPUT:
return "DEVICE_AUDIO_OUTPUT";
case MediaDeviceType::MEDIA_VIDEO_INPUT:
return "DEVICE_VIDEO_INPUT";
default:
NOTREACHED();
}
return "INVALID";
}
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();
}
return "INVALID";
}
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();
}
return "INVALID";
}
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();
}
return "INVALID";
}
const char* RequestResultToString(
blink::mojom::MediaStreamRequestResult result) {
switch (result) {
case blink::mojom::MediaStreamRequestResult::OK:
return "OK";
case blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED:
return "PERMISSION_DENIED";
case blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED:
return "PERMISSION_DISMISSED";
case blink::mojom::MediaStreamRequestResult::INVALID_STATE:
return "INVALID_STATE";
case blink::mojom::MediaStreamRequestResult::NO_HARDWARE:
return "NO_HARDWARE";
case blink::mojom::MediaStreamRequestResult::INVALID_SECURITY_ORIGIN:
return "INVALID_SECURITY_ORIGIN";
case blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE:
return "INVALID_STATE";
case blink::mojom::MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE:
return "TAB_CAPTURE_FAILURE";
case blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE:
return "CAPTURE_FAILURE";
case blink::mojom::MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED:
return "CONSTRAINT_NOT_SATISFIED";
case blink::mojom::MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO:
return "TRACK_START_FAILURE_AUDIO";
case blink::mojom::MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO:
return "TRACK_START_FAILURE_VIDEO";
case blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED:
return "NOT_SUPPORTED";
case blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN:
return "FAILED_DUE_TO_SHUTDOWN";
case blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON:
return "KILL_SWITCH_ON";
case blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED:
return "SYSTEM_PERMISSION_DENIED";
case blink::mojom::MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS:
return "NUM_MEDIA_REQUEST_RESULTS";
default:
NOTREACHED();
}
return "INVALID";
}
std::string GetGenerateStreamsLogString(int render_process_id,
int render_frame_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_process_id, render_frame_id, requester_id, page_request_id);
}
std::string GetOpenDeviceLogString(int render_process_id,
int render_frame_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_process_id, render_frame_id, requester_id, page_request_id,
device_id.c_str(), StreamTypeToString(type));
}
std::string GetStopStreamDeviceLogString(
int render_process_id,
int render_frame_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_process_id, render_frame_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. 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.
MediaStreamDevices DisplayMediaDevicesFromFakeDeviceConfig(
blink::mojom::MediaStreamType media_type,
bool request_audio,
int render_process_id,
int render_frame_id,
blink::mojom::PreferredDisplaySurface preferred_display_surface) {
MediaStreamDevices devices;
DesktopMediaID::Type desktop_media_type = DesktopMediaID::TYPE_SCREEN;
DesktopMediaID::Id desktop_media_id_id = DesktopMediaID::kNullId;
WebContentsMediaCaptureId web_contents_id;
media::mojom::DisplayCaptureSurfaceType display_surface =
media::mojom::DisplayCaptureSurfaceType::MONITOR;
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
if (command_line &&
command_line->HasSwitch(switches::kUseFakeDeviceForMediaStream)) {
std::vector<media::FakeVideoCaptureDeviceSettings> config;
media::FakeVideoCaptureDeviceFactory::
ParseFakeDevicesConfigFromOptionsString(
command_line->GetSwitchValueASCII(
switches::kUseFakeDeviceForMediaStream),
&config);
if (!config.empty()) {
desktop_media_type = DesktopMediaID::TYPE_NONE;
desktop_media_id_id = DesktopMediaID::kFakeId;
switch (config[0].display_media_type) {
case media::FakeVideoCaptureDevice::DisplayMediaType::ANY:
case media::FakeVideoCaptureDevice::DisplayMediaType::MONITOR:
desktop_media_type = DesktopMediaID::TYPE_SCREEN;
display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR;
break;
case media::FakeVideoCaptureDevice::DisplayMediaType::WINDOW:
desktop_media_type = DesktopMediaID::TYPE_WINDOW;
display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW;
break;
case media::FakeVideoCaptureDevice::DisplayMediaType::BROWSER:
desktop_media_type = DesktopMediaID::TYPE_WEB_CONTENTS;
display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER;
web_contents_id = {render_process_id, render_frame_id};
break;
}
}
}
switch (preferred_display_surface) {
case blink::mojom::PreferredDisplaySurface::NO_PREFERENCE:
break;
case blink::mojom::PreferredDisplaySurface::MONITOR:
desktop_media_type = DesktopMediaID::TYPE_SCREEN;
display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR;
break;
case blink::mojom::PreferredDisplaySurface::WINDOW:
desktop_media_type = DesktopMediaID::TYPE_WINDOW;
display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW;
break;
case blink::mojom::PreferredDisplaySurface::BROWSER:
desktop_media_type = DesktopMediaID::TYPE_WEB_CONTENTS;
display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER;
web_contents_id = {render_process_id, render_frame_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);
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);
devices.emplace_back(audio_device);
return devices;
}
void FinalizeGetMediaDeviceIDForHMAC(
MediaDeviceType type,
const std::string& salt,
const url::Origin& security_origin,
const std::string& source_id,
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::OnceCallback<void(const absl::optional<std::string>&)> callback,
const MediaDeviceEnumeration& enumeration) {
for (const auto& device : enumeration[static_cast<size_t>(type)]) {
if (MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
salt, security_origin, source_id, device.device_id)) {
task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), device.device_id));
return;
}
}
task_runner->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), absl::nullopt));
}
bool ChangeSourceSupported(const MediaStreamDevices& devices) {
for (const MediaStreamDevice& device : devices) {
DesktopMediaID media_id = DesktopMediaID::Parse(device.id);
if (media_id.type != DesktopMediaID::TYPE_WEB_CONTENTS) {
return false; // Change of source only supported between tabs.
}
}
for (const MediaStreamDevice& device : devices) {
if (device.type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) {
return true; // Established API supporting share-this-tab-instead.
}
}
if (!base::FeatureList::IsEnabled(
media::kShareThisTabInsteadButtonGetDisplayMedia)) {
return false; // Killswitch engaged.
}
if (!base::Contains(devices, MediaStreamType::DISPLAY_VIDEO_CAPTURE,
&MediaStreamDevice::type) &&
!base::Contains(devices, MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB,
&MediaStreamDevice::type)) {
return false; // Not an API call that supports share-this-tab-instead.
}
if (!base::FeatureList::IsEnabled(
media::kShareThisTabInsteadButtonGetDisplayMediaAudio) &&
base::Contains(devices, MediaStreamType::DISPLAY_AUDIO_CAPTURE,
&MediaStreamDevice::type)) {
// The user chose to capture audio, but the killswitch against
// share-this-tab-instead with audio is engaged.
return false;
}
return true; // getDisplayMedia() and killswitches did not trigger.
}
#if !BUILDFLAG(IS_ANDROID)
base::TimeDelta GetConditionalFocusWindow() {
const std::string custom_window =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
blink::switches::kConditionalFocusWindowMs);
if (!custom_window.empty()) {
int64_t ms;
if (base::StringToInt64(custom_window, &ms) && ms >= 0) {
return base::Milliseconds(ms);
} else {
LOG(ERROR) << "Could not parse custom conditional focus window.";
}
}
// If this value is changed, some of the histograms associated with
// Conditional Focus should also change.
return base::Seconds(1);
}
#endif
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class MediaStreamRequestResult2 {
kOk = 0,
kPermissionDenied = 1,
kPermissionDismissed = 2,
kInvalidState = 3,
kNoHardware = 4,
kInvalidSecurityOrigin = 5,
kTabCaptureFailure = 6,
kScreenCaptureFailure = 7,
kCaptureFailure = 8,
kConstraintNotSatisfied = 9,
kTrackStartFailureAudio = 10,
kTrackStartFailureVideo = 11,
kNotSupported = 12,
kFailedDueToShutdown = 13,
kKillSwitchOn = 14,
kSystemPermissionDenied = 15,
kDeviceInUse = 16,
kMaxValue = kDeviceInUse
};
void RecordMediaStreamRequestResult2(blink::mojom::MediaStreamType video_type,
MediaStreamRequestResult2 result2) {
switch (video_type) {
case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
base::UmaHistogramEnumeration(
"Media.MediaStreamManager.DesktopVideoDeviceUpdate", result2);
return;
case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET:
base::UmaHistogramEnumeration(
"Media.MediaStreamManager.DisplayVideoDeviceUpdate", result2);
return;
default:
return;
}
}
void RecordMediaDeviceUpdateResponseMetric(
blink::mojom::MediaStreamType video_type,
MediaStreamRequestResult result) {
switch (result) {
case MediaStreamRequestResult::OK:
RecordMediaStreamRequestResult2(video_type,
MediaStreamRequestResult2::kOk);
return;
case MediaStreamRequestResult::PERMISSION_DENIED:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kPermissionDenied);
return;
case MediaStreamRequestResult::PERMISSION_DISMISSED:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kPermissionDismissed);
return;
case MediaStreamRequestResult::INVALID_STATE:
RecordMediaStreamRequestResult2(video_type,
MediaStreamRequestResult2::kInvalidState);
return;
case MediaStreamRequestResult::NO_HARDWARE:
RecordMediaStreamRequestResult2(video_type,
MediaStreamRequestResult2::kNoHardware);
return;
case MediaStreamRequestResult::INVALID_SECURITY_ORIGIN:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kInvalidSecurityOrigin);
return;
case MediaStreamRequestResult::TAB_CAPTURE_FAILURE:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kTabCaptureFailure);
return;
case MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kScreenCaptureFailure);
return;
case MediaStreamRequestResult::CAPTURE_FAILURE:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kCaptureFailure);
return;
case MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kConstraintNotSatisfied);
return;
case MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kTrackStartFailureAudio);
return;
case MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kTrackStartFailureVideo);
return;
case MediaStreamRequestResult::NOT_SUPPORTED:
RecordMediaStreamRequestResult2(video_type,
MediaStreamRequestResult2::kNotSupported);
return;
case MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kFailedDueToShutdown);
return;
case MediaStreamRequestResult::KILL_SWITCH_ON:
RecordMediaStreamRequestResult2(video_type,
MediaStreamRequestResult2::kKillSwitchOn);
return;
case MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED:
RecordMediaStreamRequestResult2(
video_type, MediaStreamRequestResult2::kSystemPermissionDenied);
return;
case MediaStreamRequestResult::DEVICE_IN_USE:
RecordMediaStreamRequestResult2(video_type,
MediaStreamRequestResult2::kDeviceInUse);
return;
case MediaStreamRequestResult::NUM_MEDIA_REQUEST_RESULTS:
break;
}
NOTREACHED();
}
void RecordMediaStreamRequestResponseMetric(
blink::mojom::MediaStreamType video_type,
blink::MediaStreamRequestType request_type,
MediaStreamRequestResult result) {
switch (request_type) {
case blink::MEDIA_DEVICE_UPDATE:
RecordMediaDeviceUpdateResponseMetric(video_type, result);
return;
case blink::MEDIA_DEVICE_ACCESS:
case blink::MEDIA_GENERATE_STREAM:
case blink::MEDIA_GET_OPEN_DEVICE:
case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY:
return;
}
}
const blink::MediaStreamDevice* GetStreamDevice(
const blink::mojom::StreamDevices& stream_devices,
const base::UnguessableToken& session_id) {
for (const absl::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;
}
} // 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(
int requesting_process_id,
int requesting_frame_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_cb = DeviceStoppedCallback(),
DeviceChangedCallback device_changed_cb = DeviceChangedCallback(),
DeviceRequestStateChangeCallback device_request_state_change_cb =
DeviceRequestStateChangeCallback(),
DeviceCaptureHandleChangeCallback device_capture_handle_change_cb =
DeviceCaptureHandleChangeCallback())
: requesting_process_id(requesting_process_id),
requesting_frame_id(requesting_frame_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_cb(std::move(device_stopped_cb)),
device_changed_cb(std::move(device_changed_cb)),
device_request_state_change_cb(
std::move(device_request_state_change_cb)),
device_capture_handle_change_cb(
std::move(device_capture_handle_change_cb)),
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_process_id_(-1),
target_frame_id_(-1) {
DCHECK(request_type == blink::MEDIA_GENERATE_STREAM ||
request_type == blink::MEDIA_GET_OPEN_DEVICE ||
(device_changed_cb.is_null() &&
device_request_state_change_cb.is_null() &&
device_capture_handle_change_cb.is_null()));
SendLogMessage(base::StringPrintf(
"DR::DeviceRequest({requesting_process_id=%d}, "
"{requesting_frame_id=%d}, {requester_id=%d}, {request_type=%s})",
requesting_process_id, requesting_frame_id, requester_id,
RequestTypeToString(request_type)));
}
virtual ~DeviceRequest() = default;
void set_request_type(MediaStreamRequestType type) { request_type_ = type; }
MediaStreamRequestType request_type() const { return request_type_; }
const StreamControls& stream_controls() const { return stream_controls_; }
void SetAudioType(MediaStreamType audio_type) {
DCHECK(blink::IsAudioInputMediaType(audio_type) ||
audio_type == MediaStreamType::NO_SERVICE);
SendLogMessage(base::StringPrintf(
"DR::SetAudioType([requester_id=%d] {audio_type=%s})", requester_id,
StreamTypeToString(audio_type)));
audio_type_ = audio_type;
}
MediaStreamType audio_type() const { return audio_type_; }
void SetVideoType(MediaStreamType video_type) {
DCHECK(blink::IsVideoInputMediaType(video_type) ||
video_type == MediaStreamType::NO_SERVICE);
video_type_ = video_type;
}
MediaStreamType video_type() const { return video_type_; }
// Creates a MediaStreamRequest object that is used by this request when UI
// is asked for permission and device selection.
void CreateUIRequest(const std::string& requested_audio_device_id,
const std::string& requested_video_device_id) {
DCHECK(!ui_request_);
SendLogMessage(base::StringPrintf(
"DR::CreateUIRequest([requester_id=%d] {requested_audio_device_id=%s}, "
"{requested_video_device_id=%s})",
requester_id, requested_audio_device_id.c_str(),
requested_video_device_id.c_str()));
target_process_id_ = requesting_process_id;
target_frame_id_ = requesting_frame_id;
ui_request_ = std::make_unique<MediaStreamRequest>(
requesting_process_id, requesting_frame_id, page_request_id,
salt_and_origin.origin.GetURL(), user_gesture, request_type_,
requested_audio_device_id, requested_video_device_id, audio_type_,
video_type_, stream_controls_.disable_local_echo,
stream_controls_.request_pan_tilt_zoom_permission);
ui_request_->suppress_local_audio_playback =
stream_controls_.suppress_local_audio_playback;
ui_request_->exclude_system_audio = stream_controls_.exclude_system_audio;
ui_request_->exclude_self_browser_surface =
stream_controls_.exclude_self_browser_surface;
ui_request_->preferred_display_surface =
stream_controls_.preferred_display_surface;
}
// Creates a tab capture specific MediaStreamRequest object that is used by
// this request when UI is asked for permission and device selection.
void CreateTabCaptureUIRequest(int target_render_process_id,
int target_render_frame_id) {
DCHECK(!ui_request_);
target_process_id_ = target_render_process_id;
target_frame_id_ = target_render_frame_id;
ui_request_ = std::make_unique<MediaStreamRequest>(
target_render_process_id, target_render_frame_id, page_request_id,
salt_and_origin.origin.GetURL(), user_gesture, request_type_, "", "",
audio_type_, video_type_, stream_controls_.disable_local_echo,
/*request_pan_tilt_zoom_permission=*/false);
ui_request_->exclude_system_audio = stream_controls_.exclude_system_audio;
}
bool HasUIRequest() const { return ui_request_.get() != nullptr; }
std::unique_ptr<MediaStreamRequest> DetachUIRequest() {
return std::move(ui_request_);
}
// Update the request state and notify observers.
void SetState(MediaStreamType stream_type, MediaRequestState new_state) {
SendLogMessage(base::StringPrintf(
"DR::SetState([requester_id=%d] {stream_type=%s}, {new_state=%s})",
requester_id, StreamTypeToString(stream_type),
RequestStateToString(new_state)));
if (stream_type == MediaStreamType::NUM_MEDIA_TYPES) {
for (int i = static_cast<int>(MediaStreamType::NO_SERVICE) + 1;
i < static_cast<int>(MediaStreamType::NUM_MEDIA_TYPES); ++i) {
state_[i] = new_state;
}
} else {
state_[static_cast<int>(stream_type)] = new_state;
}
#if BUILDFLAG(IS_CHROMEOS)
NotifyMultiCaptureStateChanged(new_state);
#endif // BUILDFLAG(IS_CHROMEOS)
MediaObserver* media_observer =
GetContentClient()->browser()->GetMediaObserver();
if (!media_observer)
return;
if (stream_type == MediaStreamType::NUM_MEDIA_TYPES) {
for (int i = static_cast<int>(MediaStreamType::NO_SERVICE) + 1;
i < static_cast<int>(MediaStreamType::NUM_MEDIA_TYPES); ++i) {
media_observer->OnMediaRequestStateChanged(
target_process_id_, target_frame_id_, page_request_id,
salt_and_origin.origin.GetURL(), static_cast<MediaStreamType>(i),
new_state);
}
} else {
media_observer->OnMediaRequestStateChanged(
target_process_id_, target_frame_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)];
}
absl::optional<TransferState> GetTransferState(
MediaStreamType stream_type,
const base::UnguessableToken& transfer_id) {
auto transfer_map = transfer_status_map_[static_cast<int>(stream_type)];
auto it = transfer_map.find(transfer_id);
if (it == transfer_map.end()) {
return absl::nullopt;
}
return it->second.state;
}
void SetTransferState(MediaStreamType stream_type,
const base::UnguessableToken& transfer_id,
TransferState transfer_state) {
auto& transfer_map = transfer_status_map_[static_cast<int>(stream_type)];
transfer_map[transfer_id] = {transfer_state,
/*start_time=*/base::TimeTicks::Now()};
}
bool IsTransferMapEmpty(MediaStreamType stream_type) const {
return transfer_status_map_[static_cast<int>(stream_type)].empty();
}
void RemoveEntryInTransferMap(MediaStreamType stream_type,
const base::UnguessableToken& transfer_id) {
auto& transfer_map = transfer_status_map_[static_cast<int>(stream_type)];
transfer_map.erase(transfer_id);
}
void SetCapturingLinkSecured(bool is_secure) {
MediaObserver* media_observer =
GetContentClient()->browser()->GetMediaObserver();
if (!media_observer)
return;
media_observer->OnSetCapturingLinkSecured(target_process_id_,
target_frame_id_, page_request_id,
video_type_, is_secure);
}
// This function checks if the request is for the getDisplayMediaSet API.
bool IsGetDisplayMediaSet() const {
return stream_controls_.video.stream_type ==
blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET;
}
void SetLabel(const std::string& label) { label_ = label; }
void DisableAudioSharing() {
SetAudioType(MediaStreamType::NO_SERVICE);
stream_controls_.audio.stream_type = MediaStreamType::NO_SERVICE;
stream_controls_.hotword_enabled = false;
stream_controls_.disable_local_echo = false;
stream_controls_.suppress_local_audio_playback = false;
stream_controls_.exclude_system_audio = false;
}
// TODO(crbug.com/1386165): 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/1386165): Make pure virtual when there is a subclass for all
// MediaStreamRequestTypes
virtual void FinalizeRequest(const std::string& label) { NOTREACHED(); }
// TODO(crbug.com/1386165): Make pure virtual when there is a subclass for all
// MediaStreamRequestTypes
virtual void FinalizeRequestFailed(MediaStreamRequestResult result) {
NOTREACHED();
}
// The render process 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 int requesting_process_id;
// The render frame id that requested this stream to be generated and that
// will receive a handle to the MediaStream. This may be different from
// MediaStreamRequest::render_frame_id which in the tab capture case
// specifies the target renderer from which audio and video is captured.
const int requesting_frame_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;
// 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.
// Currently it is only used by |DEVICE_ACCESS| type.
MediaAccessRequestCallback media_access_request_cb;
DeviceStoppedCallback device_stopped_cb;
DeviceChangedCallback device_changed_cb;
DeviceRequestStateChangeCallback device_request_state_change_cb;
DeviceCaptureHandleChangeCallback device_capture_handle_change_cb;
std::unique_ptr<MediaStreamUIProxy> ui_proxy;
std::string tab_capture_device_id;
PermissionController::SubscriptionId audio_subscription_id;
PermissionController::SubscriptionId video_subscription_id;
private:
#if BUILDFLAG(IS_CHROMEOS)
static void NotifyMultiCaptureStarted(const std::string& label,
const url::Origin& origin) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
auto* const service = chromeos::LacrosService::Get();
if (!service->IsRegistered<crosapi::mojom::MultiCaptureService>() ||
!service->IsAvailable<crosapi::mojom::MultiCaptureService>()) {
LOG(ERROR) << "chrome.MultiCaptureService is not available in Lacros.";
return;
}
crosapi::mojom::MultiCaptureService* const multi_capture_service =
service->GetRemote<crosapi::mojom::MultiCaptureService>().get();
multi_capture_service->MultiCaptureStarted(label, origin.host());
#elif BUILDFLAG(IS_CHROMEOS_ASH)
content::GetMultiCaptureService().NotifyMultiCaptureStarted(label, origin);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
static void NotifyMultiCaptureStopped(const std::string& label) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
auto* const service = chromeos::LacrosService::Get();
if (!service->IsRegistered<crosapi::mojom::MultiCaptureService>() ||
!service->IsAvailable<crosapi::mojom::MultiCaptureService>()) {
LOG(ERROR) << "chrome.MultiCaptureService is not available in Lacros.";
return;
}
crosapi::mojom::MultiCaptureService* const multi_capture_service =
service->GetRemote<crosapi::mojom::MultiCaptureService>().get();
multi_capture_service->MultiCaptureStopped(label);
#elif BUILDFLAG(IS_CHROMEOS_ASH)
content::GetMultiCaptureService().NotifyMultiCaptureStopped(label);
#endif
}
void NotifyMultiCaptureStateChanged(MediaRequestState new_state) {
if (!IsGetDisplayMediaSet())
return;
switch (new_state) {
case MediaRequestState::MEDIA_REQUEST_STATE_OPENING:
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(NotifyMultiCaptureStarted, label_,
salt_and_origin.origin));
break;
case MediaRequestState::MEDIA_REQUEST_STATE_CLOSING:
case MediaRequestState::MEDIA_REQUEST_STATE_ERROR:
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(NotifyMultiCaptureStopped, 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 video_type_;
int target_process_id_;
int target_frame_id_;
std::string label_;
};
class MediaStreamManager::GenerateStreamsRequest
: public MediaStreamManager::DeviceRequest {
public:
GenerateStreamsRequest(
int requesting_process_id,
int requesting_frame_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_cb,
DeviceChangedCallback device_changed_cb,
DeviceRequestStateChangeCallback device_request_state_change_cb,
DeviceCaptureHandleChangeCallback device_capture_handle_change_cb,
GenerateStreamsCallback generate_streams_cb)
: DeviceRequest(requesting_process_id,
requesting_frame_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_cb),
std::move(device_changed_cb),
std::move(device_request_state_change_cb),
std::move(device_capture_handle_change_cb)),
generate_streams_cb_(std::move(generate_streams_cb)) {
DCHECK(generate_streams_cb_);
}
~GenerateStreamsRequest() override {
if (generate_streams_cb_) {
std::move(generate_streams_cb_)
.Run(MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN,
/*label=*/std::string(),
/*stream_devices_set=*/nullptr,
/*pan_tilt_zoom_allowed=*/false);
}
}
void PanTiltZoomPermissionChecked(const std::string& label,
bool pan_tilt_zoom_allowed) override {
DCHECK(generate_streams_cb_);
std::move(generate_streams_cb_)
.Run(MediaStreamRequestResult::OK, label, stream_devices_set.Clone(),
pan_tilt_zoom_allowed);
}
void FinalizeRequestFailed(MediaStreamRequestResult result) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(generate_streams_cb_);
std::move(generate_streams_cb_)
.Run(result, /*label=*/std::string(),
/*stream_devices_set=*/nullptr,
/*pan_tilt_zoom_allowed=*/false);
}
private:
GenerateStreamsCallback generate_streams_cb_;
};
class MediaStreamManager::GetOpenDeviceRequest
: public MediaStreamManager::DeviceRequest {
public:
GetOpenDeviceRequest(
int requesting_process_id,
int requesting_frame_id,
int requester_id,
int page_request_id,
MediaDeviceSaltAndOrigin salt_and_origin,
DeviceStoppedCallback device_stopped_cb,
DeviceChangedCallback device_changed_cb,
DeviceRequestStateChangeCallback device_request_state_change_cb,
DeviceCaptureHandleChangeCallback device_capture_handle_change_cb,
GetOpenDeviceCallback get_open_device_cb)
: DeviceRequest(requesting_process_id,
requesting_frame_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_cb),
std::move(device_changed_cb),
std::move(device_request_state_change_cb),
std::move(device_capture_handle_change_cb)),
get_open_device_cb_(std::move(get_open_device_cb)) {
DCHECK(get_open_device_cb_);
}
~GetOpenDeviceRequest() override {
if (get_open_device_cb_) {
std::move(get_open_device_cb_)
.Run(MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN, nullptr);
}
}
void PanTiltZoomPermissionChecked(const std::string& label,
bool pan_tilt_zoom_allowed) override {
DCHECK(get_open_device_cb_);
// GetOpenDevice is only available with exactly one stream.
DCHECK_EQ(stream_devices_set.stream_devices.size(), 1u);
const blink::mojom::StreamDevices& stream_devices =
*stream_devices_set.stream_devices[0];
// GetOpenDevice should return exactly one device, which can be of either
// audio or video type.
DCHECK_NE(stream_devices.audio_device.has_value(),
stream_devices.video_device.has_value());
MediaStreamDevice device = blink::IsVideoInputMediaType(video_type())
? stream_devices.video_device.value()
: stream_devices.audio_device.value();
std::move(get_open_device_cb_)
.Run(MediaStreamRequestResult::OK,
GetOpenDeviceResponse::New(label, device, pan_tilt_zoom_allowed));
}
void FinalizeRequestFailed(MediaStreamRequestResult result) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(get_open_device_cb_);
std::move(get_open_device_cb_).Run(result, /*response=*/nullptr);
}
private:
// 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 absl::nullopt instead of
// a MediaStreamDevice.
GetOpenDeviceCallback get_open_device_cb_;
};
class MediaStreamManager::OpenDeviceRequest
: public MediaStreamManager::DeviceRequest {
public:
OpenDeviceRequest(int requesting_process_id,
int requesting_frame_id,
int requester_id,
int page_request_id,
StreamSelectionInfoPtr audio_stream_selection_info_ptr,
const StreamControls& controls,
MediaDeviceSaltAndOrigin salt_and_origin,
DeviceStoppedCallback device_stopped_cb,
OpenDeviceCallback open_device_cb)
: DeviceRequest(requesting_process_id,
requesting_frame_id,
requester_id,
page_request_id,
/*user gesture=*/false,
std::move(audio_stream_selection_info_ptr),
blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY,
controls,
std::move(salt_and_origin),
std::move(device_stopped_cb)),
open_device_cb_(std::move(open_device_cb)) {
DCHECK(open_device_cb_);
}
~OpenDeviceRequest() override {
if (open_device_cb_) {
std::move(open_device_cb_)
.Run(/*success=*/false, std::string(), MediaStreamDevice());
}
}
void FinalizeRequest(const std::string& label) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(open_device_cb_);
SendLogMessage(base::StringPrintf(
"FinalizeOpenDevice({label=%s}, {requester_id="
"%d}, {request_type=%s})",
label.c_str(), requester_id, RequestTypeToString(request_type())));
std::move(open_device_cb_)
.Run(/*success=*/true, label,
blink::ToMediaStreamDevicesList(stream_devices_set).front());
}
void FinalizeRequestFailed(MediaStreamRequestResult result) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(open_device_cb_);
std::move(open_device_cb_)
.Run(/*success=*/false, /*label=*/std::string(), MediaStreamDevice());
}
private:
// 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_cb_;
};
// 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;
MediaStreamManager* msm = g_media_stream_manager_tls_ptr.Pointer()->Get();
if (!msm) {
// MediaStreamManager hasn't been initialized. This is allowed in tests.
return;
}
msm->AddLogMessageOnIOThread(message);
}
MediaStreamManager::MediaStreamManager(media::AudioSystem* audio_system)
: MediaStreamManager(audio_system, nullptr) {
SendLogMessage(base::StringPrintf("MediaStreamManager([this=%p]))", this));
}
MediaStreamManager::MediaStreamManager(
media::AudioSystem* audio_system,
std::unique_ptr<VideoCaptureProvider> video_capture_provider)
:
#if !BUILDFLAG(IS_ANDROID)
conditional_focus_window_(GetConditionalFocusWindow()),
#endif
audio_system_(audio_system) {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUseFakeUIForMediaStream)) {
fake_ui_factory_ = base::BindRepeating([] {
return std::make_unique<FakeMediaStreamUIProxy>(
/*tests_use_fake_render_frame_hosts=*/false);
});
}
if (base::FeatureList::IsEnabled(media::kUseFakeDeviceForMediaStream)) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kUseFakeDeviceForMediaStream);
}
DCHECK(audio_system_);
if (!video_capture_provider) {
scoped_refptr<base::SingleThreadTaskRunner> device_task_runner;
#if BUILDFLAG(IS_MAC)
// On MacOS the main thread must be used to run VideoCaptureDevice.
device_task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
#else // !BUILDFLAG(IS_MAC)
// For all platforms other than MacOS start a new thread.
video_capture_thread_.emplace("VideoCaptureThread");
base::Thread::Options thread_options;
#if BUILDFLAG(IS_WIN)
// Use an STA Video Capture Thread to try to avoid crashes on enumeration
// of buggy third party Direct Show modules, http://crbug.com/428958.
video_capture_thread_->init_com_with_mta(false);
thread_options.message_pump_type = base::MessagePumpType::UI;
#elif BUILDFLAG(IS_FUCHSIA)
// On Fuchsia IO thread is required for FIDL connections.
thread_options.message_pump_type = base::MessagePumpType::IO;
#endif
CHECK(video_capture_thread_->StartWithOptions(std::move(thread_options)));
device_task_runner = video_capture_thread_->task_runner();
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (media::ShouldUseCrosCameraService()) {
media::VideoCaptureDeviceFactoryChromeOS::SetGpuBufferManager(
GpuMemoryBufferManagerSingleton::GetInstance());
media::CameraHalDispatcherImpl::GetInstance()->Start(
base::BindRepeating(
&VideoCaptureDependencies::CreateJpegDecodeAccelerator),
base::BindRepeating(
&VideoCaptureDependencies::CreateJpegEncodeAccelerator));
}
#endif
if (base::FeatureList::IsEnabled(features::kMojoVideoCapture)) {
video_capture_provider = std::make_unique<VideoCaptureProviderSwitcher>(
std::make_unique<ServiceVideoCaptureProvider>(
base::BindRepeating(&SendVideoCaptureLogMessage)),
InProcessVideoCaptureProvider::CreateInstanceForNonDeviceCapture(
std::move(device_task_runner),
base::BindRepeating(&SendVideoCaptureLogMessage)));
} else {
video_capture_provider = InProcessVideoCaptureProvider::CreateInstance(
std::make_unique<media::VideoCaptureSystemImpl>(
media::CreateVideoCaptureDeviceFactory(
GetUIThreadTaskRunner({}))),
std::move(device_task_runner),
base::BindRepeating(&SendVideoCaptureLogMessage));
}
}
InitializeMaybeAsync(std::move(video_capture_provider));
audio_service_listener_ = std::make_unique<AudioServiceListener>();
}
MediaStreamManager::~MediaStreamManager() {
DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO));
DCHECK(requests_.empty());
}
VideoCaptureManager* MediaStreamManager::video_capture_manager() const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(video_capture_manager_.get());
return video_capture_manager_.get();
}
AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager()
const {
// May be called on any thread, provided that we are not in shutdown.
DCHECK(audio_input_device_manager_.get());
return audio_input_device_manager_.get();
}
AudioServiceListener* MediaStreamManager::audio_service_listener() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return audio_service_listener_.get();
}
MediaDevicesManager* MediaStreamManager::media_devices_manager() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// nullptr might be returned during shutdown.
return media_devices_manager_.get();
}
media::AudioSystem* MediaStreamManager::audio_system() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
return audio_system_;
}
void MediaStreamManager::AddVideoCaptureObserver(
media::VideoCaptureObserver* capture_observer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (video_capture_manager_) {
video_capture_manager_->AddVideoCaptureObserver(capture_observer);
}
}
void MediaStreamManager::RemoveAllVideoCaptureObservers() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (video_capture_manager_) {
video_capture_manager_->RemoveAllVideoCaptureObservers();
}
}
std::string MediaStreamManager::MakeMediaAccessRequest(
int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id,
const StreamControls& controls,
const url::Origin& security_origin,
MediaAccessRequestCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
StreamSelectionInfoPtr audio_stream_selection_info_ptr =
StreamSelectionInfo::New(
blink::mojom::StreamSelectionStrategy::SEARCH_BY_DEVICE_ID,
absl::nullopt);
auto request = std::make_unique<DeviceRequest>(
render_process_id, render_frame_id, requester_id, page_request_id,
false /* user gesture */, std::move(audio_stream_selection_info_ptr),
blink::MEDIA_DEVICE_ACCESS, controls,
MediaDeviceSaltAndOrigin{
std::string() /* salt */, std::string() /* group_id_salt */,
security_origin, true /* has_focus */, false /* is_background */});
request->media_access_request_cb = std::move(callback);
const std::string label = AddRequest(std::move(request));
// 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(
int render_process_id,
int render_frame_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_cb,
DeviceStoppedCallback device_stopped_cb,
DeviceChangedCallback device_changed_cb,
DeviceRequestStateChangeCallback device_request_state_change_cb,
DeviceCaptureHandleChangeCallback device_capture_handle_change_cb) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(GetGenerateStreamsLogString(render_process_id, render_frame_id,
requester_id, page_request_id));
std::unique_ptr<DeviceRequest> request =
std::make_unique<GenerateStreamsRequest>(
render_process_id, render_frame_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_cb),
std::move(device_changed_cb),
std::move(device_request_state_change_cb),
std::move(device_capture_handle_change_cb),
std::move(generate_streams_cb));
DeviceRequest* const request_ptr = request.get();
const std::string label = AddRequest(std::move(request));
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_ptr);
} else {
FinalizeRequestFailed(label, request_ptr,
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,
int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id,
MediaDeviceSaltAndOrigin salt_and_origin,
GetOpenDeviceCallback get_open_device_cb,
DeviceStoppedCallback device_stopped_cb,
DeviceChangedCallback device_changed_cb,
DeviceRequestStateChangeCallback device_request_state_change_cb,
DeviceCaptureHandleChangeCallback device_capture_handle_change_cb) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer));
std::unique_ptr<DeviceRequest> request =
std::make_unique<GetOpenDeviceRequest>(
render_process_id, render_frame_id, requester_id, page_request_id,
std::move(salt_and_origin), std::move(device_stopped_cb),
std::move(device_changed_cb),
std::move(device_request_state_change_cb),
std::move(device_capture_handle_change_cb),
std::move(get_open_device_cb));
DeviceRequest* const request_ptr = request.get();
const std::string new_label = AddRequest(std::move(request));
const absl::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.
FinalizeRequestFailed(new_label, request_ptr,
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(int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (const LabeledDeviceRequest& labeled_request : requests_) {
DeviceRequest* const request = labeled_request.second.get();
if (request->requesting_process_id == render_process_id &&
request->requesting_frame_id == render_frame_id &&
request->requester_id == requester_id &&
request->page_request_id == page_request_id) {
CancelRequest(labeled_request.first);
return;
}
}
}
void MediaStreamManager::CancelRequest(const std::string& label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(
base::StringPrintf("CancelRequest({label=%s})", label.c_str()));
DeviceRequest* request = FindRequest(label);
if (!request) {
// The request does not exist.
LOG(ERROR) << "The request with label = " << label << " does not exist.";
return;
}
// 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 absl::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(label);
}
void MediaStreamManager::CancelAllRequests(int render_process_id,
int render_frame_id,
int requester_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto request_it = requests_.begin();
while (request_it != requests_.end()) {
if (request_it->second->requesting_process_id != render_process_id ||
request_it->second->requesting_frame_id != render_frame_id ||
request_it->second->requester_id != requester_id) {
++request_it;
continue;
}
const std::string label = request_it->first;
++request_it;
CancelRequest(label);
}
}
void MediaStreamManager::StopStreamDevice(
int render_process_id,
int render_frame_id,
int requester_id,
const std::string& device_id,
const base::UnguessableToken& session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(GetStopStreamDeviceLogString(
render_process_id, render_frame_id, requester_id, device_id, session_id));
// Find the first request for this |render_process_id| and |render_frame_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_process_id != render_process_id ||
request->requesting_frame_id != render_frame_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(
int render_process_id,
int render_frame_id,
int requester_id,
const base::UnguessableToken& session_id,
const base::UnguessableToken& transfer_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(base::FeatureList::IsEnabled(features::kMediaStreamTrackTransfer));
for (const LabeledDeviceRequest& device_request : requests_) {
DeviceRequest* const request = device_request.second.get();
switch (request->request_type()) {
case blink::MEDIA_DEVICE_ACCESS:
case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY:
break;
case blink::MEDIA_DEVICE_UPDATE:
case blink::MEDIA_GENERATE_STREAM:
case blink::MEDIA_GET_OPEN_DEVICE:
for (const auto& stream_devices_ptr :
request->stream_devices_set.stream_devices) {
const blink::MediaStreamDevice* const device =
GetStreamDevice(*stream_devices_ptr, session_id);
if (!device)
continue;
UpdateDeviceTransferStatus(request, device, transfer_id,
TransferState::KEPT_ALIVE);
return true;
}
break;
}
}
return false;
}
base::UnguessableToken MediaStreamManager::VideoDeviceIdToSessionId(
const std::string& device_id) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (const LabeledDeviceRequest& device_request : requests_) {
for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr :
device_request.second->stream_devices_set.stream_devices) {
const blink::mojom::StreamDevices& devices = *stream_devices_ptr;
if (devices.video_device.has_value() &&
devices.video_device->id == device_id &&
devices.video_device->type == MediaStreamType::DEVICE_VIDEO_CAPTURE) {
return devices.video_device->session_id();
}
}
}
return base::UnguessableToken();
}
void MediaStreamManager::StopDevice(MediaStreamType type,
const base::UnguessableToken& session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf("StopDevice({type=%s}, {session_id=%s})",
StreamTypeToString(type),
session_id.ToString().c_str()));
auto 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 = absl::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 = absl::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 std::string& label = request_it->first;
++request_it;
DeleteRequest(label);
} else {
++request_it;
}
}
}
void MediaStreamManager::CloseDevice(MediaStreamType type,
const base::UnguessableToken& session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf("CloseDevice({type=%s}, {session_id=%s})",
StreamTypeToString(type),
session_id.ToString().c_str()));
GetDeviceManager(type)->Close(session_id);
for (const LabeledDeviceRequest& labeled_request : requests_) {
DeviceRequest* const request = labeled_request.second.get();
for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr :
request->stream_devices_set.stream_devices) {
const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr;
for (const absl::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_cb) {
request->device_stopped_cb.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(int render_process_id,
int render_frame_id,
int requester_id,
int page_request_id,
const std::string& device_id,
MediaStreamType type,
MediaDeviceSaltAndOrigin salt_and_origin,
OpenDeviceCallback open_device_cb,
DeviceStoppedCallback device_stopped_cb) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(type == MediaStreamType::DEVICE_AUDIO_CAPTURE ||
type == MediaStreamType::DEVICE_VIDEO_CAPTURE);
SendLogMessage(GetOpenDeviceLogString(render_process_id, render_frame_id,
requester_id, page_request_id,
device_id, type));
StreamControls controls;
if (blink::IsAudioInputMediaType(type)) {
controls.audio.stream_type = type;
controls.audio.device_id = device_id;
} else if (blink::IsVideoInputMediaType(type)) {
controls.video.stream_type = type;
controls.video.device_id = device_id;
} else {
NOTREACHED();
}
// 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.
StreamSelectionInfoPtr audio_stream_selection_info_ptr =
StreamSelectionInfo::New(
blink::mojom::StreamSelectionStrategy::SEARCH_BY_DEVICE_ID,
absl::nullopt);
auto request = std::make_unique<OpenDeviceRequest>(
render_process_id, render_frame_id, requester_id, page_request_id,
std::move(audio_stream_selection_info_ptr), controls,
std::move(salt_and_origin), std::move(device_stopped_cb),
std::move(open_device_cb));
const std::string label = AddRequest(std::move(request));
// 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));
}
bool MediaStreamManager::TranslateSourceIdToDeviceIdAndGroupId(
MediaStreamType stream_type,
const std::string& salt,
const url::Origin& security_origin,
const std::string& source_id,
std::string* device_id,
absl::optional<std::string>* group_id) const {
DCHECK(stream_type == MediaStreamType::DEVICE_AUDIO_CAPTURE ||
stream_type == MediaStreamType::DEVICE_VIDEO_CAPTURE);
// The source_id can be empty if the constraint is set but empty.
if (source_id.empty())
return false;
// TODO(guidou): Change to use MediaDevicesManager::EnumerateDevices.
// See http://crbug.com/648155.
blink::WebMediaDeviceInfoArray cached_devices =
media_devices_manager_->GetCachedDeviceInfo(
ConvertToMediaDeviceType(stream_type));
return GetDeviceIDAndGroupIDFromHMAC(salt, security_origin, source_id,
cached_devices, device_id, group_id);
}
void MediaStreamManager::EnsureDeviceMonitorStarted() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media_devices_manager_->StartMonitoring();
}
void MediaStreamManager::StopRemovedDevice(
MediaDeviceType type,
const blink::WebMediaDeviceInfo& media_device_info) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(type == MediaDeviceType::MEDIA_AUDIO_INPUT ||
type == MediaDeviceType::MEDIA_VIDEO_INPUT);
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 absl::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 = GetHMACForMediaDeviceID(
request->salt_and_origin.device_id_salt,
request->salt_and_origin.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_cb) {
request->device_stopped_cb.Run(labeled_request.first, device);
}
}
}
}
}
for (const auto& session_id : session_ids)
StopDevice(stream_type, session_id);
}
bool MediaStreamManager::PickDeviceId(
const MediaDeviceSaltAndOrigin& salt_and_origin,
const TrackControls& controls,
const blink::WebMediaDeviceInfoArray& devices,
std::string* device_id) const {
if (controls.device_id.empty())
return true;
if (!GetDeviceIDAndGroupIDFromHMAC(
salt_and_origin.device_id_salt, salt_and_origin.origin,
controls.device_id, devices, device_id, /*group_id=*/nullptr)) {
LOG(WARNING) << "Invalid device ID = " << controls.device_id;
return false;
}
return true;
}
bool MediaStreamManager::GetRequestedDeviceCaptureId(
const DeviceRequest* request,
MediaStreamType type,
const blink::WebMediaDeviceInfoArray& devices,
std::string* device_id) const {
if (type == MediaStreamType::DEVICE_AUDIO_CAPTURE) {
return PickDeviceId(request->salt_and_origin,
request->stream_controls().audio, devices, device_id);
} else if (type == MediaStreamType::DEVICE_VIDEO_CAPTURE) {
return PickDeviceId(request->salt_and_origin,
request->stream_controls().video, devices, device_id);
} else {
NOTREACHED();
}
return false;
}
void MediaStreamManager::TranslateDeviceIdToSourceId(
const DeviceRequest* request,
MediaStreamDevice* device) const {
if (request->audio_type() == MediaStreamType::DEVICE_AUDIO_CAPTURE ||
request->video_type() == MediaStreamType::DEVICE_VIDEO_CAPTURE) {
device->id =
GetHMACForMediaDeviceID(request->salt_and_origin.device_id_salt,
request->salt_and_origin.origin, device->id);
if (device->group_id) {
device->group_id = GetHMACForMediaDeviceID(
request->salt_and_origin.group_id_salt,
request->salt_and_origin.origin, *device->group_id);
}
}
}
void MediaStreamManager::StartEnumeration(DeviceRequest* request,
const std::string& label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(
base::StringPrintf("StartEnumeration({requester_id=%d}, {label=%s})",
request->requester_id, label.c_str()));
// Start monitoring the devices when doing the first enumeration.
media_devices_manager_->StartMonitoring();
// Start enumeration for devices of all requested device types.
bool request_audio_input =
request->audio_type() != MediaStreamType::NO_SERVICE;
if (request_audio_input)
request->SetState(request->audio_type(), MEDIA_REQUEST_STATE_REQUESTED);
bool request_video_input =
request->video_type() != MediaStreamType::NO_SERVICE;
if (request_video_input)
request->SetState(request->video_type(), MEDIA_REQUEST_STATE_REQUESTED);
// base::Unretained is safe here because MediaStreamManager is deleted on the
// UI thread, after the IO thread has been stopped.
DCHECK(request_audio_input || request_video_input);
MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
devices_to_enumerate[static_cast<size_t>(
MediaDeviceType::MEDIA_AUDIO_INPUT)] = request_audio_input;
devices_to_enumerate[static_cast<size_t>(
MediaDeviceType::MEDIA_VIDEO_INPUT)] = request_video_input;
media_devices_manager_->EnumerateDevices(
devices_to_enumerate,
base::BindOnce(&MediaStreamManager::DevicesEnumerated,
base::Unretained(this), request_audio_input,
request_video_input, label));
}
std::string MediaStreamManager::AddRequest(
std::unique_ptr<DeviceRequest> request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Create a label for this request and verify it is unique.
std::string unique_label;
do {
unique_label = RandomLabel();
} 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 unique_label;
}
MediaStreamManager::DeviceRequest* MediaStreamManager::FindRequest(
const std::string& label) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (const LabeledDeviceRequest& labeled_request : requests_) {
if (labeled_request.first == label)
return labeled_request.second.get();
}
return nullptr;
}
absl::optional<MediaStreamDevice> MediaStreamManager::CloneExistingOpenDevice(
const base::UnguessableToken& existing_device_session_id,
const base::UnguessableToken& transfer_id,
const std::string& new_label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* const new_request = FindRequest(new_label);
DCHECK(new_request);
// TODO(crbug.com/1334583): Generalize to multiple streams.
DCHECK(new_request->stream_devices_set.stream_devices.empty());
for (const LabeledDeviceRequest& labeled_request : requests_) {
DeviceRequest* const existing_request = labeled_request.second.get();
// Skipping requests that contain multiple streams.
// TODO(crbug.com/1334583): 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(https://crbug.com/1288839): 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(https://crbug.com/1288839): Remove bad message after transfer
// is supported for these stream types.
// TODO(https://crbug.com/1288839): Hash device id and group_id for
// MediaStreamType DEVICE_AUDIO_CAPTURE and DEVICE_VIDEO_CAPTURE.
ReceivedBadMessage(
new_request->requesting_process_id,
bad_message::MSM_GET_OPEN_DEVICE_FOR_UNSUPPORTED_STREAM_TYPE);
return absl::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 absl::nullopt;
}
void MediaStreamManager::UpdateDeviceTransferStatus(
DeviceRequest* request,
const blink::MediaStreamDevice* const device,
const base::UnguessableToken& transfer_id,
TransferState transfer_state) {
// TODO(https://crbug.com/1288839): Use |start_time| to enforce a timeout to
// stop device in case a transfer never completes.
MediaStreamType stream_type = device->type;
absl::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::DeleteRequest(const std::string& label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(
base::StringPrintf("DeleteRequest([label=%s])", label.c_str()));
for (auto request_it = requests_.begin(); request_it != requests_.end();
++request_it) {
if (request_it->first == label) {
// Clean up permission controller subscription.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&MediaStreamManager::
UnsubscribeFromPermissionControllerOnUIThread,
request_it->second->requesting_process_id,
request_it->second->requesting_frame_id,
request_it->second->audio_subscription_id,
request_it->second->video_subscription_id));
requests_.erase(request_it);
return;
}
}
NOTREACHED();
}
void MediaStreamManager::ReadOutputParamsAndPostRequestToUI(
const std::string& label,
DeviceRequest* request,
const MediaDeviceEnumeration& enumeration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Actual audio parameters are required only for
// MEDIA_GUM_TAB_AUDIO_CAPTURE.
if (request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
// Using base::Unretained is safe: |audio_system_| will post
// PostRequestToUI() to IO thread, and MediaStreamManager is deleted on the
// UI thread, after the IO thread has been stopped.
audio_system_->GetOutputStreamParameters(
media::AudioDeviceDescription::kDefaultDeviceId,
base::BindOnce(&MediaStreamManager::PostRequestToUI,
base::Unretained(this), label, enumeration));
} else {
PostRequestToUI(label, enumeration,
absl::optional<media::AudioParameters>());
}
}
void MediaStreamManager::PostRequestToUI(
const std::string& label,
const MediaDeviceEnumeration& enumeration,
const absl::optional<media::AudioParameters>& output_parameters) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!output_parameters || output_parameters->IsValid());
DeviceRequest* request = FindRequest(label);
if (!request)
return;
DCHECK(request->HasUIRequest());
SendLogMessage(
base::StringPrintf("PostRequestToUI({label=%s}, ", label.c_str()));
const MediaStreamType audio_type = request->audio_type();
const MediaStreamType video_type = request->video_type();
// Post the request to UI and set the state.
if (blink::IsAudioInputMediaType(audio_type))
request->SetState(audio_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
if (blink::IsVideoInputMediaType(video_type))
request->SetState(video_type, MEDIA_REQUEST_STATE_PENDING_APPROVAL);
if (ShouldUseFakeUIProxy(request->video_type())) {
request->ui_proxy = MakeFakeUIProxy(label, enumeration, request);
} else if (!request->ui_proxy) {
request->ui_proxy = MediaStreamUIProxy::Create();
}
request->ui_proxy->RequestAccess(
request->DetachUIRequest(),
base::BindOnce(&MediaStreamManager::HandleAccessRequestResponse,
base::Unretained(this), label,
output_parameters.value_or(
media::AudioParameters::UnavailableDeviceParams())));
}
void MediaStreamManager::SetUpRequest(const std::string& label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request) {
DVLOG(1) << "SetUpRequest label " << label << " doesn't exist!!";
return; // This can happen if the request has been canceled.
}
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;
if (is_display_capture && !SetUpDisplayCaptureRequest(request)) {
FinalizeRequestFailed(label, request,
MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE);
return;
}
const bool is_tab_capture =
request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE ||
request->video_type() == MediaStreamType::GUM_TAB_VIDEO_CAPTURE;
if (is_tab_capture) {
if (!SetUpTabCaptureRequest(request, label)) {
FinalizeRequestFailed(label, request,
MediaStreamRequestResult::TAB_CAPTURE_FAILURE);
}
return;
}
const bool is_screen_capture =
request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
if (is_screen_capture && !SetUpScreenCaptureRequest(request)) {
FinalizeRequestFailed(label, request,
MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE);
return;
}
if (!is_tab_capture && !is_screen_capture && !is_display_capture) {
if (blink::IsDeviceMediaType(request->audio_type()) ||
blink::IsDeviceMediaType(request->video_type())) {
StartEnumeration(request, label);
return;
}
// If no actual device capture is requested, set up the request with an
// empty device list.
if (!SetUpDeviceCaptureRequest(request, MediaDeviceEnumeration())) {
FinalizeRequestFailed(label, request,
MediaStreamRequestResult::NO_HARDWARE);
return;
}
}
if (request->stream_controls().request_all_screens) {
std::unique_ptr<media::ScreenEnumerator> screen_enumerator =
GetContentClient()->browser()->CreateScreenEnumerator();
if (!screen_enumerator) {
HandleAccessRequestResponse(
label, media::AudioParameters(), blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED);
return;
}
// The screen enumerator lives on the IO thread.
// It is safe to bind base::Unretained(this) because MediaStreamManager is
// owned by BrowserMainLoop.
screen_enumerator->EnumerateScreens(
request->video_type(),
base::BindOnce(&MediaStreamManager::HandleAccessRequestResponse,
base::Unretained(this), label,
media::AudioParameters()));
return;
}
ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration());
}
bool MediaStreamManager::SetUpDisplayCaptureRequest(DeviceRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE ||
request->video_type() ==
MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB ||
request->video_type() == MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET);
// 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.requested() ||
!request->stream_controls().video.device_id.empty() ||
!request->stream_controls().audio.device_id.empty()) {
LOG(ERROR) << "Invalid display media request.";
return false;
}
request->CreateUIRequest(std::string() /* requested_audio_device_id */,
std::string() /* requested_video_device_id */);
DVLOG(3) << "Audio requested " << request->stream_controls().audio.requested()
<< " Video requested "
<< request->stream_controls().video.requested();
return true;
}
bool MediaStreamManager::SetUpDeviceCaptureRequest(
DeviceRequest* request,
const MediaDeviceEnumeration& enumeration) {
DCHECK((request->audio_type() == MediaStreamType::DEVICE_AUDIO_CAPTURE ||
request->audio_type() == MediaStreamType::NO_SERVICE) &&
(request->video_type() == MediaStreamType::DEVICE_VIDEO_CAPTURE ||
request->video_type() == MediaStreamType::NO_SERVICE));
SendLogMessage(base::StringPrintf(
"SetUpDeviceCaptureRequest([requester_id=%d])", request->requester_id));
std::string audio_device_id;
if (request->stream_controls().audio.requested() &&
!GetRequestedDeviceCaptureId(
request, request->audio_type(),
enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_AUDIO_INPUT)],
&audio_device_id)) {
return false;
}
std::string video_device_id;
if (request->stream_controls().video.requested() &&
!GetRequestedDeviceCaptureId(
request, request->video_type(),
enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_VIDEO_INPUT)],
&video_device_id)) {
return false;
}
request->CreateUIRequest(audio_device_id, video_device_id);
DVLOG(3) << "Audio requested " << request->stream_controls().audio.requested()
<< " device id = " << audio_device_id << "Video requested "
<< request->stream_controls().video.requested()
<< " device id = " << video_device_id;
return true;
}
bool MediaStreamManager::SetUpTabCaptureRequest(DeviceRequest* request,
const std::string& label) {
DCHECK(request->audio_type() == MediaStreamType::GUM_TAB_AUDIO_CAPTURE ||
request->video_type() == MediaStreamType::GUM_TAB_VIDEO_CAPTURE);
std::string capture_device_id;
if (!request->stream_controls().audio.device_id.empty()) {
capture_device_id = request->stream_controls().audio.device_id;
} else if (!request->stream_controls().video.device_id.empty()) {
capture_device_id = request->stream_controls().video.device_id;
} 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_process_id,
request->requesting_frame_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,
int requesting_process_id,
int requesting_frame_id,
const GURL& origin) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Resolve DesktopMediaID for the specified device id.
return DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId(
capture_device_id, requesting_process_id, requesting_frame_id,
url::Origin::Create(origin), nullptr, kRegistryStreamTypeTab);
}
void MediaStreamManager::FinishTabCaptureRequestSetupWithDeviceId(
const std::string& label,
const DesktopMediaID& device_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request) {
DVLOG(1) << "SetUpRequest label " << label << " doesn't exist!!";
return; // This can happen if the request has been canceled.
}
// Received invalid device id.
if (device_id.type != content::DesktopMediaID::TYPE_WEB_CONTENTS) {
FinalizeRequestFailed(label, request,
MediaStreamRequestResult::TAB_CAPTURE_FAILURE);
return;
}
content::WebContentsMediaCaptureId web_id = device_id.web_contents_id;
web_id.disable_local_echo = request->stream_controls().disable_local_echo;
request->tab_capture_device_id = web_id.ToString();
request->CreateTabCaptureUIRequest(web_id.render_process_id,
web_id.main_render_frame_id);
DVLOG(3) << "SetUpTabCaptureRequest "
<< ", {capture_device_id = " << web_id.ToString() << "}"
<< ", {target_render_process_id = " << web_id.render_process_id
<< "}"
<< ", {target_render_frame_id = " << web_id.main_render_frame_id
<< "}";
ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration());
}
bool MediaStreamManager::SetUpScreenCaptureRequest(DeviceRequest* request) {
DCHECK(request->audio_type() == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE ||
request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE);
// For screen capture we only support two valid combinations:
// (1) screen video capture only, or
// (2) screen video capture with loopback audio capture.
if (request->video_type() != MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
(request->audio_type() != MediaStreamType::NO_SERVICE &&
request->audio_type() != MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE)) {
LOG(ERROR) << "Invalid screen capture request.";
return false;
}
std::string video_device_id;
if (request->video_type() == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE &&
!request->stream_controls().video.device_id.empty()) {
video_device_id = request->stream_controls().video.device_id;
}
const std::string audio_device_id =
request->audio_type() == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE
? video_device_id
: "";
request->CreateUIRequest(audio_device_id, video_device_id);
return true;
}
void MediaStreamManager::SetUpDesktopCaptureChangeSourceRequest(
DeviceRequest* request,
const std::string& label,
const DesktopMediaID& media_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(blink::IsDesktopCaptureMediaType(request->video_type()));
DCHECK(request->request_type() == blink::MEDIA_GENERATE_STREAM ||
request->request_type() == blink::MEDIA_DEVICE_UPDATE);
// Set up request type to bring up the picker again within a session.
request->set_request_type(blink::MEDIA_DEVICE_UPDATE);
request->CreateUIRequest(
std::string() /* requested_audio_device_id */,
media_id.is_null() ? std::string()
: media_id.ToString() /* requested_video_device_id */);
ReadOutputParamsAndPostRequestToUI(label, request, MediaDeviceEnumeration());
}
MediaStreamDevices MediaStreamManager::GetDevicesOpenedByRequest(
const std::string& label) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request)
return MediaStreamDevices();
return blink::ToMediaStreamDevicesList(request->stream_devices_set);
}
bool MediaStreamManager::FindExistingRequestedDevice(
const DeviceRequest& new_request,
const MediaStreamDevice& new_device,
MediaStreamDevice* existing_device,
MediaRequestState* existing_request_state) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(existing_device);
DCHECK(existing_request_state);
std::string hashed_source_id = GetHMACForMediaDeviceID(
new_request.salt_and_origin.device_id_salt,
new_request.salt_and_origin.origin, new_device.id);
bool is_audio_capture =
new_device.type == MediaStreamType::DEVICE_AUDIO_CAPTURE &&
new_request.audio_type() == MediaStreamType::DEVICE_AUDIO_CAPTURE;
StreamSelectionStrategy strategy =
new_request.audio_stream_selection_info_ptr->strategy;
if (is_audio_capture &&
strategy == blink::mojom::StreamSelectionStrategy::FORCE_NEW_STREAM) {
return false;
}
absl::optional<base::UnguessableToken> requested_session_id =
new_request.audio_stream_selection_info_ptr->session_id;
#if DCHECK_IS_ON()
if (strategy == StreamSelectionStrategy::SEARCH_BY_SESSION_ID) {
DCHECK(requested_session_id);
DCHECK(!requested_session_id->is_empty());
}
#endif
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_process_id == new_request.requesting_process_id &&
request->requesting_frame_id == new_request.requesting_frame_id &&
request->request_type() == new_request.request_type()) {
for (const absl::optional<blink::MediaStreamDevice>* device_ptr : {
&stream_devices.audio_device,
&stream_devices.video_device,
}) {
if (!device_ptr->has_value()) {
continue;
}
const blink::MediaStreamDevice& device = device_ptr->value();
const bool is_same_device =
device.id == hashed_source_id && device.type == new_device.type;
// If |strategy| is equal to SEARCH_BY_DEVICE_ID, the
// search is performed only based on the |device.id|. If, however,
// |strategy| is equal to SEARCH_BY_SESSION_ID, 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 ||
strategy == StreamSelectionStrategy::SEARCH_BY_DEVICE_ID ||
(strategy == StreamSelectionStrategy::SEARCH_BY_SESSION_ID &&
device.session_id() == *requested_session_id);
if (is_same_device && is_same_session) {
*existing_device = device;
// Make sure that the audio |effects| reflect what the request
// is set to and not what the capabilities are.
int effects = existing_device->input.effects();
FilterAudioEffects(request->stream_controls(), &effects);
EnableHotwordEffect(request->stream_controls(), &effects);
existing_device->input.set_effects(effects);
*existing_request_state = request->state(device.type);
return true;
}
}
}
}
}
return false;
}
void MediaStreamManager::FinalizeGenerateStreams(const std::string& label,
DeviceRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request);
DCHECK_EQ(request->request_type(), blink::MEDIA_GENERATE_STREAM);
SendLogMessage(
base::StringPrintf("FinalizeGenerateStreams({label=%s}, {requester_id="
"%d}, {request_type=%s})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type())));
// Subscribe to follow permission changes in order to close streams when the
// user denies mic/camera.
SubscribeToPermissionController(label, request);
blink::mojom::StreamDevicesSetPtr stream_devices_set =
request->stream_devices_set.Clone();
if (request->IsGetDisplayMediaSet()) {
PanTiltZoomPermissionChecked(label, MediaStreamDevice(),
/*pan_tilt_zoom_allowed=*/false);
return;
}
// TODO(crbug.com/1300883): Generalize to multiple streams.
DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size());
// It is safe to bind base::Unretained(this) because MediaStreamManager is
// owned by BrowserMainLoop and so outlives the IO thread.
// TODO(crbug.com/1314741): Avoid using PTZ permission checks for non-gUM
// tracks.
GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&MediaDevicesPermissionChecker::
HasPanTiltZoomPermissionGrantedOnUIThread,
request->requesting_process_id,
request->requesting_frame_id),
base::BindOnce(
&MediaStreamManager::PanTiltZoomPermissionChecked,
base::Unretained(this), label,
request->stream_devices_set.stream_devices[0]->video_device));
}
void MediaStreamManager::FinalizeGetOpenDevice(const std::string& label,
DeviceRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request);
DCHECK_EQ(request->request_type(), blink::MEDIA_GET_OPEN_DEVICE);
SendLogMessage(
base::StringPrintf("FinalizeGetOpenDevice({label=%s}, {requester_id="
"%d}, {request_type=%s})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type())));
// Subscribe to follow permission changes in order to close streams when the
// user denies mic/camera.
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/1314743): Avoid this check once you have this permission
// value from original context.
GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&MediaDevicesPermissionChecker::
HasPanTiltZoomPermissionGrantedOnUIThread,
request->requesting_process_id,
request->requesting_frame_id),
base::BindOnce(
&MediaStreamManager::PanTiltZoomPermissionChecked,
base::Unretained(this), label,
request->stream_devices_set.stream_devices[0]->video_device));
}
// TODO(https://crbug.com/1288839): Ensure CaptureHandle works for transferred
// MediaStreamTracks and add tests for the same.
// TODO(https://crbug.com/1314634): Ensure track transfer does not initiate
// focus-change with Conditional focus enabled.
void MediaStreamManager::PanTiltZoomPermissionChecked(
const std::string& label,
const absl::optional<MediaStreamDevice>& video_device,
bool pan_tilt_zoom_allowed) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request)
return;
SendLogMessage(base::StringPrintf(
"PanTiltZoomPermissionChecked({label=%s}, {requester_id="
"%d}, {request_type=%s}, {pan_tilt_zoom_allowed=%d})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type()), pan_tilt_zoom_allowed));
request->PanTiltZoomPermissionChecked(label, pan_tilt_zoom_allowed);
if (request->IsGetDisplayMediaSet())
return;
#if !BUILDFLAG(IS_ANDROID)
// 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(),
GlobalRenderFrameHostId(request->requesting_process_id,
request->requesting_frame_id));
}
}
void MediaStreamManager::FinalizeRequestFailed(
const std::string& label,
DeviceRequest* request,
MediaStreamRequestResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf(
"FinalizeRequestFailed({label=%s}, {requester_id=%d}, {result=%s})",
label.c_str(), request->requester_id, RequestResultToString(result)));
switch (request->request_type()) {
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_ACCESS: {
DCHECK(request->media_access_request_cb);
std::move(request->media_access_request_cb)
.Run(/*stream_devices_set=*/blink::mojom::StreamDevicesSet(),
std::move(request->ui_proxy));
break;
}
case blink::MEDIA_DEVICE_UPDATE: {
// Fail to change capture source, keep everything unchanged and
// bring the previous shared tab to the front.
DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size());
const blink::mojom::StreamDevices& devices =
*request->stream_devices_set.stream_devices[0];
if (devices.video_device.has_value()) {
const blink::MediaStreamDevice& device = devices.video_device.value();
DCHECK_NE(device.type, MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET);
// TODO(crbug.com/1334332): Also consider
// DISPLAY_VIDEO_CAPTURE_THIS_TAB.
if (device.type == MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE ||
device.type == MediaStreamType::DISPLAY_VIDEO_CAPTURE) {
DesktopMediaID source = DesktopMediaID::Parse(device.id);
DCHECK(source.type == DesktopMediaID::TYPE_WEB_CONTENTS);
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&MediaStreamManager::ActivateTabOnUIThread,
base::Unretained(this), source));
}
}
return;
}
default:
NOTREACHED();
break;
}
DeleteRequest(label);
}
void MediaStreamManager::FinalizeChangeDevice(const std::string& label,
DeviceRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request->device_changed_cb);
DCHECK_EQ(1u, request->old_stream_devices_set.stream_devices.size());
DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size());
const blink::mojom::StreamDevices& old_devices =
*request->old_stream_devices_set.stream_devices[0];
const blink::mojom::StreamDevices& new_devices =
*request->stream_devices_set.stream_devices[0];
SendLogMessage(
base::StringPrintf("FinalizeChangeDevice({label=%s}, {requester_id="
"%d}, {request_type=%s})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type())));
std::vector<std::vector<MediaStreamDevice>> old_devices_by_type(
static_cast<size_t>(MediaStreamType::NUM_MEDIA_TYPES));
for (const absl::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 absl::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();
}
request->device_changed_cb.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)
request->device_changed_cb.Run(label, old_device, MediaStreamDevice());
MaybeUpdateTrackedCaptureHandleConfigs(
label, request->stream_devices_set,
GlobalRenderFrameHostId(request->requesting_process_id,
request->requesting_frame_id));
}
void MediaStreamManager::FinalizeMediaAccessRequest(
const std::string& label,
DeviceRequest* request,
const blink::mojom::StreamDevicesSet& stream_devices_set) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request->media_access_request_cb);
SendLogMessage(
base::StringPrintf("FinalizeMediaAccessRequest({label=%s}, {requester_id="
"%d}, {request_type=%s})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type())));
std::move(request->media_access_request_cb)
.Run(stream_devices_set, std::move(request->ui_proxy));
// Delete the request since it is done.
DeleteRequest(label);
}
void MediaStreamManager::SetRequestDevice(
blink::mojom::StreamDevices& target_devices,
const blink::MediaStreamDevice& device) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (blink::IsAudioInputMediaType(device.type)) {
target_devices.audio_device = device;
} else {
DCHECK(blink::IsVideoInputMediaType(device.type));
target_devices.video_device = device;
}
}
void MediaStreamManager::InitializeMaybeAsync(
std::unique_ptr<VideoCaptureProvider> video_capture_provider) {
// Some unit tests initialize the MSM in the IO thread and assume the
// initialization is done synchronously. Other clients call this from a
// different thread and expect initialization to run asynchronously.
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&MediaStreamManager::InitializeMaybeAsync,
base::Unretained(this),
std::move(video_capture_provider)));
return;
}
SendLogMessage(base::StringPrintf("InitializeMaybeAsync([this=%p])", this));
// Store a pointer to |this| on the IO thread to avoid having to jump to
// the UI thread to fetch a pointer to the MSM. In particular on Android,
// it can be problematic to post to a UI thread from arbitrary worker
// threads since attaching to the VM is required and we may have to access
// the MSM from callback threads that we don't own and don't want to
// attach.
g_media_stream_manager_tls_ptr.Pointer()->Set(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)));
}
void MediaStreamManager::Opened(
MediaStreamType stream_type,
const base::UnguessableToken& capture_session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf("Opened({stream_type=%s}, {session_id=%s})",
StreamTypeToString(stream_type),
capture_session_id.ToString().c_str()));
// Find the request(s) containing this device and mark it as used.
// It can be used in several requests since the same device can be
// requested from the same web page.
for (const LabeledDeviceRequest& labeled_request : requests_) {
const std::string& label = labeled_request.first;
DeviceRequest* request = labeled_request.second.get();
if (request->stream_devices_set.stream_devices.empty())
continue;
// It can happen that a previous stream already failed and set an error,
// in which case this streams request does not need to be handled further.
if (request->state(stream_type) == MEDIA_REQUEST_STATE_ERROR) {
continue;
}
for (blink::mojom::StreamDevicesPtr& stream_devices_ptr :
request->stream_devices_set.stream_devices) {
blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr;
for (absl::optional<blink::MediaStreamDevice>* device_ptr : {
&stream_devices.audio_device,
&stream_devices.video_device,
}) {
if (!device_ptr->has_value()) {
continue;
}
blink::MediaStreamDevice& device = device_ptr->value();
if (device.type == stream_type &&
device.session_id() == capture_session_id) {
if (request->state(device.type) == MEDIA_REQUEST_STATE_DONE) {
continue;
}
// We've found a matching request.
CHECK_EQ(request->state(device.type), MEDIA_REQUEST_STATE_OPENING);
request->SetDeviceOpened(device.type);
if (request->devices_opened_count(device.type) ==
request->stream_devices_set.stream_devices.size()) {
request->SetState(device.type, MEDIA_REQUEST_STATE_DONE);
request->ResetDevicesOpened(device.type);
}
if (blink::IsAudioInputMediaType(device.type)) {
// Store the native audio parameters in the device struct.
// TODO(xians): Handle the tab capture sample rate/channel layout
// in AudioInputDeviceManager::Open().
if (device.type != MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
const MediaStreamDevice* opened_device =
audio_input_device_manager_->GetOpenedDeviceById(
device.session_id());
device.input = opened_device->input;
// Since the audio input device manager will set the input
// parameters to the default settings (including supported
// effects), we need to adjust those settings here according to
// what the request asks for.
int effects = device.input.effects();
FilterAudioEffects(request->stream_controls(), &effects);
EnableHotwordEffect(request->stream_controls(), &effects);
device.input.set_effects(effects);
}
}
if (RequestDone(*request))
HandleRequestDone(label, request);
break;
}
}
}
}
}
void MediaStreamManager::HandleRequestDone(const std::string& label,
DeviceRequest* request) {
DCHECK(RequestDone(*request));
switch (request->request_type()) {
case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY:
request->FinalizeRequest(label);
OnStreamStarted(label);
break;
case blink::MEDIA_GENERATE_STREAM: {
FinalizeGenerateStreams(label, request);
if (base::FeatureList::IsEnabled(
blink::features::kStartMediaStreamCaptureIndicatorInBrowser)) {
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();
break;
}
}
void MediaStreamManager::Closed(
MediaStreamType stream_type,
const base::UnguessableToken& capture_session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf("Closed({stream_type=%s}, {session_id=%s})",
StreamTypeToString(stream_type),
capture_session_id.ToString().c_str()));
}
void MediaStreamManager::DevicesEnumerated(
bool requested_audio_input,
bool requested_video_input,
const std::string& label,
const MediaDeviceEnumeration& enumeration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request)
return;
SendLogMessage(base::StringPrintf(
"DevicesEnumerated({label=%s}, {requester_id=%d}, {request_type=%s})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type())));
bool requested[] = {requested_audio_input, requested_video_input};
MediaStreamType stream_types[] = {MediaStreamType::DEVICE_AUDIO_CAPTURE,
MediaStreamType::DEVICE_VIDEO_CAPTURE};
for (size_t i = 0; i < std::size(requested); ++i) {
if (!requested[i])
continue;
DCHECK(request->audio_type() == stream_types[i] ||
request->video_type() == stream_types[i]);
if (request->state(stream_types[i]) == MEDIA_REQUEST_STATE_REQUESTED) {
request->SetState(stream_types[i], MEDIA_REQUEST_STATE_PENDING_APPROVAL);
}
}
if (!SetUpDeviceCaptureRequest(request, enumeration))
FinalizeRequestFailed(label, request,
MediaStreamRequestResult::NO_HARDWARE);
else
ReadOutputParamsAndPostRequestToUI(label, request, enumeration);
}
void MediaStreamManager::Aborted(
MediaStreamType stream_type,
const base::UnguessableToken& capture_session_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf(
"Aborted({stream_type=%s}, {session_id=%s})",
StreamTypeToString(stream_type), capture_session_id.ToString().c_str()));
StopDevice(stream_type, capture_session_id);
}
void MediaStreamManager::UseFakeUIFactoryForTests(
base::RepeatingCallback<std::unique_ptr<FakeMediaStreamUIProxy>(void)>
fake_ui_factory,
bool use_for_gum_desktop_capture) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
fake_ui_factory_ = std::move(fake_ui_factory);
use_fake_ui_for_gum_desktop_capture_ = use_for_gum_desktop_capture;
}
// static
void MediaStreamManager::RegisterNativeLogCallback(
int renderer_host_id,
base::RepeatingCallback<void(const std::string&)> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
MediaStreamManager* msm = g_media_stream_manager_tls_ptr.Pointer()->Get();
if (!msm) {
DLOG(ERROR) << "No MediaStreamManager on the IO thread.";
return;
}
msm->DoNativeLogCallbackRegistration(renderer_host_id, std::move(callback));
}
// static
void MediaStreamManager::UnregisterNativeLogCallback(int renderer_host_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
MediaStreamManager* msm = g_media_stream_manager_tls_ptr.Pointer()->Get();
if (!msm) {
DLOG(ERROR) << "No MediaStreamManager on the IO thread.";
return;
}
msm->DoNativeLogCallbackUnregistration(renderer_host_id);
}
void MediaStreamManager::AddLogMessageOnIOThread(const std::string& message) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (const auto& callback : log_callbacks_)
callback.second.Run(message);
}
void MediaStreamManager::HandleAccessRequestResponse(
const std::string& label,
const media::AudioParameters& output_parameters,
const blink::mojom::StreamDevicesSet& stream_devices_set,
MediaStreamRequestResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK((result == MediaStreamRequestResult::OK &&
!stream_devices_set.stream_devices.empty()) ||
(result != MediaStreamRequestResult::OK &&
stream_devices_set.stream_devices.empty()));
DeviceRequest* request = FindRequest(label);
if (!request) {
// The request has been canceled before the UI returned.
return;
}
SendLogMessage(base::StringPrintf(
"HandleAccessRequestResponse({label=%s}, {request=%s}, {result=%s})",
label.c_str(), RequestTypeToString(request->request_type()),
RequestResultToString(result)));
RecordMediaStreamRequestResponseMetric(request->video_type(),
request->request_type(), result);
if (request->request_type() == blink::MEDIA_DEVICE_ACCESS) {
FinalizeMediaAccessRequest(label, request, stream_devices_set);
return;
}
// Handle the case when the request was denied.
if (result != MediaStreamRequestResult::OK) {
FinalizeRequestFailed(label, request, result);
return;
}
DCHECK(base::ranges::all_of(
stream_devices_set.stream_devices,
[](const blink::mojom::StreamDevicesPtr& stream_devices) {
return stream_devices->audio_device.has_value() ||
stream_devices->video_device.has_value();
}));
if (request->request_type() == blink::MEDIA_DEVICE_UPDATE) {
HandleChangeSourceRequestResponse(label, request, stream_devices_set);
return;
}
// Process all newly-accepted devices for this request.
bool found_audio = false;
bool found_video = false;
for (size_t stream_index = request->stream_devices_set.stream_devices.size();
stream_index < stream_devices_set.stream_devices.size();
++stream_index) {
request->stream_devices_set.stream_devices.push_back(
blink::mojom::StreamDevices::New());
}
for (size_t stream_index = 0;
stream_index < stream_devices_set.stream_devices.size();
++stream_index) {
const blink::mojom::StreamDevicesPtr& stream_devices_ptr =
stream_devices_set.stream_devices[stream_index];
const blink::mojom::StreamDevices& devices = *stream_devices_ptr;
for (const absl::optional<blink::MediaStreamDevice>* device_ptr :
{&devices.audio_device, &devices.video_device}) {
if (!device_ptr->has_value()) {
continue;
}
MediaStreamDevice device = device_ptr->value();
if (device.type == MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
device.type == MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
device.id = request->tab_capture_device_id;
}
// Initialize the sample_rate and channel_layout here since for audio
// mirroring, we don't go through EnumerateDevices where these are usually
// initialized.
if (device.type == MediaStreamType::GUM_TAB_AUDIO_CAPTURE ||
device.type == MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE) {
int sample_rate = output_parameters.sample_rate();
// If we weren't able to get the native sampling rate or the sample_rate
// is outside the valid range for input devices set reasonable defaults.
if (sample_rate <= 0 || sample_rate > 96000)
sample_rate = 44100;
media::AudioParameters params(
device.input.format(), media::ChannelLayoutConfig::Stereo(),
sample_rate, device.input.frames_per_buffer());
params.set_effects(device.input.effects());
params.set_mic_positions(device.input.mic_positions());
DCHECK(params.IsValid());
device.input = params;
}
if (device.type == request->audio_type())
found_audio = true;
else if (device.type == request->video_type())
found_video = true;
// If this is request for a new MediaStream, a device is only opened once
// per render frame. This is so that the permission to use a device can be
// revoked by a single call to StopStreamDevice regardless of how many
// MediaStreams it is being used in.
if (request->request_type() == blink::MEDIA_GENERATE_STREAM) {
MediaRequestState state;
if (FindExistingRequestedDevice(*request, device, &device, &state)) {
SetRequestDevice(
*request->stream_devices_set.stream_devices[stream_index],
device);
request->SetState(device.type, state);
SendLogMessage(base::StringPrintf(
"HandleAccessRequestResponse([label=%s]) => "
"(already opened device: [id: %s, session_id: %s])",
label.c_str(), device.id.c_str(),
device.session_id().ToString().c_str()));
continue;
}
}
device.set_session_id(GetDeviceManager(device.type)->Open(device));
TranslateDeviceIdToSourceId(request, &device);
SetRequestDevice(
*request->stream_devices_set.stream_devices[stream_index], device);
const MediaRequestState current_state = request->state(device.type);
if (current_state != MEDIA_REQUEST_STATE_OPENING &&
current_state != MEDIA_REQUEST_STATE_ERROR) {
request->SetState(device.type, MEDIA_REQUEST_STATE_OPENING);
}
SendLogMessage(
base::StringPrintf("HandleAccessRequestResponse([label=%s]) => "
"(opening device: [id: %s, session_id: %s])",
label.c_str(), device.id.c_str(),
device.session_id().ToString().c_str()));
}
}
// If the user does not choose to share audio, the audio device is not
// added. In this case the audio type needs to be set to NO_SERVICE so that no
// audio device is added if a change-source is requested (i.e., if the
// share-this-tab-instead button is clicked). (Resolves crbug.com/1378910)
if (!found_audio) {
request->DisableAudioSharing();
}
// Check whether we've received all stream types requested.
if (!found_audio && blink::IsAudioInputMediaType(request->audio_type())) {
request->SetState(request->audio_type(), MEDIA_REQUEST_STATE_ERROR);
DVLOG(1) << "Set no audio found label " << label;
}
if (!found_video && blink::IsVideoInputMediaType(request->video_type()))
request->SetState(request->video_type(), MEDIA_REQUEST_STATE_ERROR);
if (RequestDone(*request))
HandleRequestDone(label, request);
}
void MediaStreamManager::HandleChangeSourceRequestResponse(
const std::string& label,
DeviceRequest* request,
const blink::mojom::StreamDevicesSet& stream_devices_set) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(request->stream_devices_set.stream_devices.size(), 1u);
DCHECK_LE(request->old_stream_devices_set.stream_devices.size(), 1u);
DCHECK_EQ(stream_devices_set.stream_devices.size(), 1u);
DVLOG(1) << "HandleChangeSourceRequestResponse("
<< ", {label = " << label << "})";
if (request->old_stream_devices_set.stream_devices.empty()) {
request->old_stream_devices_set.stream_devices.emplace_back(
blink::mojom::StreamDevices::New());
}
std::swap(request->old_stream_devices_set.stream_devices,
request->stream_devices_set.stream_devices);
const blink::mojom::StreamDevices& devices =
*stream_devices_set.stream_devices[0];
for (const absl::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);
}
void MediaStreamManager::StopMediaStreamFromBrowser(const std::string& label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request)
return;
SendLogMessage(base::StringPrintf("StopMediaStreamFromBrowser({label=%s})",
label.c_str()));
// Notify renderers that the devices in the stream will be stopped.
if (request->device_stopped_cb) {
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 absl::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_cb.Run(label, device);
}
}
}
CancelRequest(label);
IncrementDesktopCaptureCounter(DESKTOP_CAPTURE_NOTIFICATION_STOP);
}
void MediaStreamManager::ChangeMediaStreamSourceFromBrowser(
const std::string& label,
const DesktopMediaID& media_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request)
return;
DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size());
const blink::mojom::StreamDevices& devices =
*request->stream_devices_set.stream_devices[0];
if (request->ui_proxy) {
for (const absl::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);
}
}
}
SendLogMessage(base::StringPrintf(
"ChangeMediaStreamSourceFromBrowser({label=%s})", label.c_str()));
SetUpDesktopCaptureChangeSourceRequest(request, label, media_id);
IncrementDesktopCaptureCounter(DESKTOP_CAPTURE_NOTIFICATION_CHANGE_SOURCE);
}
void MediaStreamManager::RequestStateChangeFromBrowser(
const std::string& label,
const DesktopMediaID& media_id,
blink::mojom::MediaStreamStateChange new_state) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* request = FindRequest(label);
if (!request)
return;
SendLogMessage(base::StringPrintf("RequestStateChangeFromBrowser({label=%s})",
label.c_str()));
if (request->device_request_state_change_cb) {
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 absl::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) {
request->device_request_state_change_cb.Run(label, device, new_state);
}
}
}
}
}
void MediaStreamManager::WillDestroyCurrentMessageLoop() {
DVLOG(3) << "MediaStreamManager::WillDestroyCurrentMessageLoop()";
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO) ||
!BrowserThread::IsThreadInitialized(BrowserThread::IO));
if (media_devices_manager_)
media_devices_manager_->StopMonitoring();
if (video_capture_manager_)
video_capture_manager_->UnregisterListener(this);
if (audio_input_device_manager_)
audio_input_device_manager_->UnregisterListener(this);
audio_input_device_manager_ = nullptr;
video_capture_manager_ = nullptr;
media_devices_manager_ = nullptr;
g_media_stream_manager_tls_ptr.Pointer()->Set(nullptr);
requests_.clear();
}
void MediaStreamManager::NotifyDevicesChanged(
MediaDeviceType device_type,
const blink::WebMediaDeviceInfoArray& devices) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf("NotifyDevicesChanged({device_type=%s})",
DeviceTypeToString(device_type)));
MediaObserver* media_observer =
GetContentClient()->browser()->GetMediaObserver();
MediaStreamType stream_type = ConvertToMediaStreamType(device_type);
MediaStreamDevices new_devices =
ConvertToMediaStreamDevices(stream_type, devices);
if (blink::IsAudioInputMediaType(stream_type)) {
MediaCaptureDevicesImpl::GetInstance()->OnAudioCaptureDevicesChanged(
new_devices);
if (media_observer)
media_observer->OnAudioCaptureDevicesChanged();
} else if (blink::IsVideoInputMediaType(stream_type)) {
MediaCaptureDevicesImpl::GetInstance()->OnVideoCaptureDevicesChanged(
new_devices);
if (media_observer)
media_observer->OnVideoCaptureDevicesChanged();
} else {
NOTREACHED();
}
}
bool MediaStreamManager::RequestDone(const DeviceRequest& request) const {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
SendLogMessage(base::StringPrintf(
"RequestDone({requester_id=%d}, {request_type=%s})", request.requester_id,
RequestTypeToString(request.request_type())));
const bool requested_audio =
blink::IsAudioInputMediaType(request.audio_type());
const bool requested_video =
blink::IsVideoInputMediaType(request.video_type());
const bool audio_done =
!requested_audio ||
request.state(request.audio_type()) == MEDIA_REQUEST_STATE_DONE ||
request.state(request.audio_type()) == MEDIA_REQUEST_STATE_ERROR;
if (!audio_done)
return false;
const bool video_done =
!requested_video ||
request.state(request.video_type()) == MEDIA_REQUEST_STATE_DONE ||
request.state(request.video_type()) == MEDIA_REQUEST_STATE_ERROR;
if (!video_done)
return false;
return true;
}
MediaStreamProvider* MediaStreamManager::GetDeviceManager(
MediaStreamType stream_type) const {
if (blink::IsVideoInputMediaType(stream_type))
return video_capture_manager();
else if (blink::IsAudioInputMediaType(stream_type))
return audio_input_device_manager();
NOTREACHED();
return nullptr;
}
void MediaStreamManager::OnMediaStreamUIWindowId(
MediaStreamType video_type,
blink::mojom::StreamDevicesSetPtr stream_devices_set,
gfx::NativeViewId window_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!window_id)
return;
if (!blink::IsVideoDesktopCaptureMediaType(video_type))
return;
// Pass along for desktop screen and window capturing when
// DesktopCaptureDevice is used.
for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr :
stream_devices_set->stream_devices) {
const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr;
for (const absl::optional<blink::MediaStreamDevice>* device_ptr : {
&stream_devices.audio_device,
&stream_devices.video_device,
}) {
if (!device_ptr->has_value()) {
continue;
}
const blink::MediaStreamDevice& device = device_ptr->value();
if (!blink::IsVideoDesktopCaptureMediaType(device.type))
continue;
DesktopMediaID media_id = DesktopMediaID::Parse(device.id);
// WebContentsVideoCaptureDevice is used for tab/webcontents.
if (media_id.type == DesktopMediaID::TYPE_WEB_CONTENTS)
continue;
#if defined(USE_AURA)
// DesktopCaptureDeviceAura is used when aura_id is valid.
if (media_id.window_id > DesktopMediaID::kNullId)
continue;
#endif
video_capture_manager_->SetDesktopCaptureWindowId(device.session_id(),
window_id);
break;
}
}
}
void MediaStreamManager::DoNativeLogCallbackRegistration(
int renderer_host_id,
base::RepeatingCallback<void(const std::string&)> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Re-registering (overwriting) is allowed and happens in some tests.
log_callbacks_[renderer_host_id] = std::move(callback);
}
void MediaStreamManager::DoNativeLogCallbackUnregistration(
int renderer_host_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
log_callbacks_.erase(renderer_host_id);
}
// static
std::string MediaStreamManager::GetHMACForMediaDeviceID(
const std::string& salt,
const url::Origin& security_origin,
const std::string& raw_unique_id) {
// TODO(crbug.com/1215532): DCHECKs are disabled during automated testing on
// CrOS and this check failed when tested on an experimental builder. Revert
// https://crrev.com/c/2932244 to enable it. See go/chrome-dcheck-on-cros
// or http://crbug.com/1113456 for more details.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
DCHECK(!raw_unique_id.empty());
#endif
if (raw_unique_id == media::AudioDeviceDescription::kDefaultDeviceId ||
raw_unique_id == media::AudioDeviceDescription::kCommunicationsDeviceId) {
return raw_unique_id;
}
crypto::HMAC hmac(crypto::HMAC::SHA256);
const size_t digest_length = hmac.DigestLength();
std::vector<uint8_t> digest(digest_length);
bool result = hmac.Init(security_origin.Serialize()) &&
hmac.Sign(raw_unique_id + salt, &digest[0], digest.size());
DCHECK(result);
return base::ToLowerASCII(base::HexEncode(&digest[0], digest.size()));
}
// static
bool MediaStreamManager::DoesMediaDeviceIDMatchHMAC(
const std::string& salt,
const url::Origin& security_origin,
const std::string& device_guid,
const std::string& raw_unique_id) {
DCHECK(!raw_unique_id.empty());
std::string guid_from_raw_device_id =
GetHMACForMediaDeviceID(salt, security_origin, raw_unique_id);
return guid_from_raw_device_id == device_guid;
}
// static
void MediaStreamManager::GetMediaDeviceIDForHMAC(
MediaStreamType stream_type,
std::string salt,
url::Origin security_origin,
std::string hmac_device_id,
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::OnceCallback<void(const absl::optional<std::string>&)> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(stream_type == MediaStreamType::DEVICE_AUDIO_CAPTURE ||
stream_type == MediaStreamType::DEVICE_VIDEO_CAPTURE);
MediaDeviceType device_type = ConvertToMediaDeviceType(stream_type);
MediaStreamManager::GetMediaDeviceIDForHMAC(
device_type, std::move(salt), std::move(security_origin),
std::move(hmac_device_id), std::move(task_runner), std::move(callback));
}
void MediaStreamManager::GetMediaDeviceIDForHMAC(
MediaDeviceType device_type,
std::string salt,
url::Origin security_origin,
std::string hmac_device_id,
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::OnceCallback<void(const absl::optional<std::string>&)> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
MediaStreamManager* msm = g_media_stream_manager_tls_ptr.Pointer()->Get();
MediaDevicesManager::BoolDeviceTypes requested_types;
requested_types[static_cast<size_t>(device_type)] = true;
msm->media_devices_manager()->EnumerateDevices(
requested_types,
base::BindOnce(&FinalizeGetMediaDeviceIDForHMAC, device_type,
std::move(salt), std::move(security_origin),
std::move(hmac_device_id), std::move(task_runner),
std::move(callback)));
}
// static
bool MediaStreamManager::IsOriginAllowed(int render_process_id,
const url::Origin& origin) {
if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanRequestURL(
render_process_id, origin.GetURL())) {
LOG(ERROR) << "MSM: Renderer requested a URL it's not allowed to use: "
<< origin.Serialize();
return false;
}
return true;
}
void MediaStreamManager::SetCapturingLinkSecured(
int render_process_id,
const base::UnguessableToken& session_id,
MediaStreamType type,
bool is_secure) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (LabeledDeviceRequest& labeled_request : requests_) {
DeviceRequest* request = labeled_request.second.get();
if (request->requesting_process_id != render_process_id)
continue;
for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr :
request->stream_devices_set.stream_devices) {
const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr;
for (const absl::optional<blink::MediaStreamDevice>* device_ptr : {
&stream_devices.audio_device,
&stream_devices.video_device,
}) {
if (!device_ptr->has_value()) {
continue;
}
const blink::MediaStreamDevice& device = device_ptr->value();
if (device.session_id() == session_id && device.type == type) {
request->SetCapturingLinkSecured(is_secure);
return;
}
}
}
}
}
void MediaStreamManager::SetStateForTesting(
size_t request_index,
blink::mojom::MediaStreamType stream_type,
MediaRequestState new_state) {
DCHECK_LT(request_index, requests_.size());
auto requests_iterator = requests_.begin();
std::advance(requests_iterator, request_index);
requests_iterator->second->SetState(stream_type, new_state);
}
void MediaStreamManager::SetGenerateStreamsCallbackForTesting(
GenerateStreamTestCallback test_callback) {
generate_stream_test_callback_ = std::move(test_callback);
}
MediaStreamDevices MediaStreamManager::ConvertToMediaStreamDevices(
MediaStreamType stream_type,
const blink::WebMediaDeviceInfoArray& device_infos) {
MediaStreamDevices devices;
for (const auto& info : device_infos) {
devices.emplace_back(
stream_type, info.device_id, info.label, info.video_control_support,
static_cast<media::VideoFacingMode>(info.video_facing), info.group_id);
}
return devices;
}
void MediaStreamManager::ActivateTabOnUIThread(const DesktopMediaID source) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* rfh =
RenderFrameHostImpl::FromID(source.web_contents_id.render_process_id,
source.web_contents_id.main_render_frame_id);
if (rfh)
rfh->render_view_host()->GetDelegate()->Activate();
}
void MediaStreamManager::OnStreamStarted(const std::string& label) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* const request = FindRequest(label);
if (!request)
return;
SendLogMessage(base::StringPrintf(
"OnStreamStarted({label=%s}, {requester_id=%d}, {request_type=%s})",
label.c_str(), request->requester_id,
RequestTypeToString(request->request_type())));
MediaStreamUI::SourceCallback device_changed_cb;
if (request->stream_controls().dynamic_surface_switching_requested &&
ChangeSourceSupported(
blink::ToMediaStreamDevicesList(request->stream_devices_set)) &&
base::FeatureList::IsEnabled(features::kDesktopCaptureChangeSource)) {
device_changed_cb = 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 absl::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_cb,
base::BindOnce(&MediaStreamManager::OnMediaStreamUIWindowId,
base::Unretained(this), request->video_type(),
request->stream_devices_set.Clone()),
label, screen_share_ids,
base::BindRepeating(&MediaStreamManager::RequestStateChangeFromBrowser,
base::Unretained(this), label));
}
}
void MediaStreamManager::OnRegionCaptureRectChanged(
const base::UnguessableToken& session_id,
const absl::optional<gfx::Rect>& region_capture_rect) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (const LabeledDeviceRequest& labeled_device_request : requests_) {
DeviceRequest* const device_request = labeled_device_request.second.get();
if (!device_request || !device_request->ui_proxy) {
continue;
}
for (const blink::mojom::StreamDevicesPtr& stream_devices_ptr :
labeled_device_request.second->stream_devices_set.stream_devices) {
const blink::mojom::StreamDevices& stream_devices = *stream_devices_ptr;
for (const absl::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(IS_ANDROID)
void MediaStreamManager::SetCapturedDisplaySurfaceFocus(
const std::string& label,
bool focus,
bool is_from_microtask,
bool is_from_timer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* const request = FindRequest(label);
if (!request) {
return;
}
if (!request->ui_proxy) {
return;
}
DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size());
const blink::mojom::StreamDevices& devices =
*request->stream_devices_set.stream_devices[0];
DesktopMediaID media_id;
for (const absl::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
// static
PermissionControllerImpl* MediaStreamManager::GetPermissionController(
int requesting_process_id,
int requesting_frame_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHost* rfh =
RenderFrameHost::FromID(requesting_process_id, requesting_frame_id);
if (!rfh)
return nullptr;
return PermissionControllerImpl::FromBrowserContext(rfh->GetBrowserContext());
}
void MediaStreamManager::SubscribeToPermissionController(
const std::string& label,
const DeviceRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request);
// It is safe to bind base::Unretained(this) because MediaStreamManager is
// owned by BrowserMainLoop.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&MediaStreamManager::SubscribeToPermissionControllerOnUIThread,
base::Unretained(this), label, request->requesting_process_id,
request->requesting_frame_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,
int requesting_process_id,
int requesting_frame_id,
int requester_id,
int page_request_id,
bool is_audio_request,
bool is_video_request,
const GURL& origin) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PermissionControllerImpl* controller =
GetPermissionController(requesting_process_id, requesting_frame_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->SubscribePermissionStatusChange(
blink::PermissionType::AUDIO_CAPTURE,
/*render_process_host=*/nullptr,
RenderFrameHost::FromID(requesting_process_id, requesting_frame_id),
origin,
base::BindRepeating(&MediaStreamManager::PermissionChangedCallback,
base::Unretained(this), requesting_process_id,
requesting_frame_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->SubscribePermissionStatusChange(
blink::PermissionType::VIDEO_CAPTURE,
/*render_process_host=*/nullptr,
RenderFrameHost::FromID(requesting_process_id, requesting_frame_id),
origin,
base::BindRepeating(&MediaStreamManager::PermissionChangedCallback,
base::Unretained(this), requesting_process_id,
requesting_frame_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_process_id,
requesting_frame_id, audio_subscription_id,
video_subscription_id));
}
void MediaStreamManager::SetPermissionSubscriptionIDs(
const std::string& label,
int requesting_process_id,
int requesting_frame_id,
PermissionController::SubscriptionId audio_subscription_id,
PermissionController::SubscriptionId video_subscription_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* const request = FindRequest(label);
if (!request) {
// Something happened with the request while the permission subscription was
// created, unsubscribe to clean up.
// It is safe to bind base::Unretained(this) because MediaStreamManager is
// owned by BrowserMainLoop.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&MediaStreamManager::UnsubscribeFromPermissionControllerOnUIThread,
requesting_process_id, requesting_frame_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(
int requesting_process_id,
int requesting_frame_id,
PermissionController::SubscriptionId audio_subscription_id,
PermissionController::SubscriptionId video_subscription_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PermissionControllerImpl* controller =
GetPermissionController(requesting_process_id, requesting_frame_id);
if (!controller)
return;
controller->UnsubscribePermissionStatusChange(audio_subscription_id);
controller->UnsubscribePermissionStatusChange(video_subscription_id);
}
void MediaStreamManager::PermissionChangedCallback(
int requesting_process_id,
int requesting_frame_id,
int requester_id,
int page_request_id,
blink::mojom::PermissionStatus status) {
if (status == blink::mojom::PermissionStatus::GRANTED)
return;
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
// It is safe to bind base::Unretained(this) because MediaStreamManager is
// owned by BrowserMainLoop.
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&MediaStreamManager::PermissionChangedCallback,
base::Unretained(this), requesting_process_id,
requesting_frame_id, requester_id, page_request_id,
status));
return;
}
CancelRequest(requesting_process_id, requesting_frame_id, requester_id,
page_request_id);
}
void MediaStreamManager::MaybeStartTrackingCaptureHandleConfig(
const std::string& label,
const MediaStreamDevice& captured_device,
GlobalRenderFrameHostId capturer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!blink::IsVideoInputMediaType(captured_device.type) ||
!WebContentsMediaCaptureId::Parse(captured_device.id, nullptr)) {
return;
}
// It is safe to bind base::Unretained(this) because MediaStreamManager is
// owned by BrowserMainLoop.
// Since |capture_handle_manager_| is owned by |this|, it is also safe to
// bind base::Unretained(&capture_handle_manager_).
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&CaptureHandleManager::OnTabCaptureStarted,
base::Unretained(&capture_handle_manager_), label, captured_device,
capturer,
base::BindPostTask(
GetIOThreadTaskRunner({}),
base::BindRepeating(&MediaStreamManager::OnCaptureHandleChange,
base::Unretained(this)))));
}
void MediaStreamManager::MaybeStopTrackingCaptureHandleConfig(
const std::string& label,
const MediaStreamDevice& captured_device) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!blink::IsVideoInputMediaType(captured_device.type) ||
!WebContentsMediaCaptureId::Parse(captured_device.id, nullptr)) {
return;
}
// It is safe to bind base::Unretained(&capture_handle_manager_) because
// it is owned by MediaStreamManager, which is in turn owned by
// BrowserMainLoop.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CaptureHandleManager::OnTabCaptureStopped,
base::Unretained(&capture_handle_manager_),
label, captured_device));
}
void MediaStreamManager::MaybeUpdateTrackedCaptureHandleConfigs(
const std::string& label,
const blink::mojom::StreamDevicesSet& new_devices_set,
GlobalRenderFrameHostId capturer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_EQ(1u, new_devices_set.stream_devices.size());
const blink::mojom::StreamDevices& new_devices =
*new_devices_set.stream_devices[0];
blink::mojom::StreamDevicesSetPtr filtered_new_devices_set =
blink::mojom::StreamDevicesSet::New();
filtered_new_devices_set->stream_devices.emplace_back(
blink::mojom::StreamDevices::New());
blink::mojom::StreamDevices& filtered_new_devices =
*filtered_new_devices_set->stream_devices[0];
if (new_devices.video_device.has_value() &&
WebContentsMediaCaptureId::Parse(new_devices.video_device->id, nullptr)) {
filtered_new_devices.video_device = new_devices.video_device.value();
}
// It is safe to bind base::Unretained(&capture_handle_manager_) because
// it is owned by MediaStreamManager, which is in turn owned by
// BrowserMainLoop.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&CaptureHandleManager::OnTabCaptureDevicesUpdated,
base::Unretained(&capture_handle_manager_), label,
std::move(filtered_new_devices_set), capturer,
base::BindPostTask(
GetIOThreadTaskRunner({}),
base::BindRepeating(&MediaStreamManager::OnCaptureHandleChange,
base::Unretained(this)))));
}
void MediaStreamManager::OnCaptureHandleChange(
const std::string& label,
blink::mojom::MediaStreamType type,
media::mojom::CaptureHandlePtr capture_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeviceRequest* const request = FindRequest(label);
if (!request) {
DVLOG(1) << "The request with label = " << label << " does not exist.";
return;
}
DCHECK_EQ(1u, request->stream_devices_set.stream_devices.size());
const blink::mojom::StreamDevices& devices =
*request->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 (request->device_capture_handle_change_cb) {
request->device_capture_handle_change_cb.Run(label, *device);
}
}
bool MediaStreamManager::ShouldUseFakeUIProxy(
MediaStreamType stream_type) const {
return fake_ui_factory_ &&
(stream_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_process_id, request->requesting_frame_id,
request->stream_controls().preferred_display_surface);
} 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::MEDIA_AUDIO_INPUT)]);
MediaStreamDevices video_devices = ConvertToMediaStreamDevices(
request->video_type(),
enumeration[static_cast<size_t>(MediaDeviceType::MEDIA_VIDEO_INPUT)]);
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->SetAvailableDevices(devices);
return fake_ui;
}
} // namespace content