blob: 9f1c338c3094409ddfe99795da20e59c6ab1e02d [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/mediastream/media_stream_device_observer.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "third_party/blink/public/platform/interface_registry.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/mediastream/user_media_processor.h"
namespace blink {
namespace {
bool RemoveStreamDeviceFromArray(const MediaStreamDevice& device,
MediaStreamDevices* devices) {
for (auto device_it = devices->begin(); device_it != devices->end();
++device_it) {
if (device_it->IsSameDevice(device)) {
devices->erase(device_it);
return true;
}
}
return false;
}
} // namespace
MediaStreamDeviceObserver::MediaStreamDeviceObserver(LocalFrame* frame) {
// There is no frame on unit tests.
if (frame) {
frame->GetInterfaceRegistry()->AddInterface(WTF::BindRepeating(
&MediaStreamDeviceObserver::BindMediaStreamDeviceObserverReceiver,
WTF::Unretained(this)));
}
}
MediaStreamDeviceObserver::~MediaStreamDeviceObserver() {}
MediaStreamDevices MediaStreamDeviceObserver::GetNonScreenCaptureDevices() {
MediaStreamDevices video_devices;
for (const auto& stream_it : label_stream_map_) {
for (const auto& video_device : stream_it.value.video_devices) {
if (!IsScreenCaptureMediaType(video_device.type))
video_devices.push_back(video_device);
}
}
return video_devices;
}
void MediaStreamDeviceObserver::OnDeviceStopped(
const String& label,
const MediaStreamDevice& device) {
DVLOG(1) << __func__ << " label=" << label << " device_id=" << device.id;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = label_stream_map_.find(label);
if (it == label_stream_map_.end()) {
// This can happen if a user stops a device from JS at the same
// time as the underlying media device is unplugged from the system.
return;
}
Stream* stream = &it->value;
if (IsAudioInputMediaType(device.type))
RemoveStreamDeviceFromArray(device, &stream->audio_devices);
else
RemoveStreamDeviceFromArray(device, &stream->video_devices);
if (stream->on_device_stopped_cb)
stream->on_device_stopped_cb.Run(device);
// |it| could have already been invalidated in the function call above. So we
// need to check if |label| is still in |label_stream_map_| again.
// Note: this is a quick fix to the crash caused by erasing the invalidated
// iterator from |label_stream_map_| (https://crbug.com/616884). Future work
// needs to be done to resolve this re-entrancy issue.
it = label_stream_map_.find(label);
if (it == label_stream_map_.end())
return;
stream = &it->value;
if (stream->audio_devices.empty() && stream->video_devices.empty())
label_stream_map_.erase(it);
}
void MediaStreamDeviceObserver::OnDeviceChanged(
const String& label,
const MediaStreamDevice& old_device,
const MediaStreamDevice& new_device) {
DVLOG(1) << __func__ << " old_device_id=" << old_device.id
<< " new_device_id=" << new_device.id;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = label_stream_map_.find(label);
if (it == label_stream_map_.end()) {
// This can happen if a user stops a device from JS at the same
// time as the underlying media device is unplugged from the system.
return;
}
Stream* stream = &it->value;
if (stream->on_device_changed_cb)
stream->on_device_changed_cb.Run(old_device, new_device);
// Update device list only for device changing. Removing device will be
// handled in its own callback.
if (old_device.type != mojom::MediaStreamType::NO_SERVICE &&
new_device.type != mojom::MediaStreamType::NO_SERVICE) {
if (RemoveStreamDeviceFromArray(old_device, &stream->audio_devices) ||
RemoveStreamDeviceFromArray(old_device, &stream->video_devices)) {
if (IsAudioInputMediaType(new_device.type))
stream->audio_devices.push_back(new_device);
else
stream->video_devices.push_back(new_device);
}
}
}
void MediaStreamDeviceObserver::OnDeviceRequestStateChange(
const String& label,
const MediaStreamDevice& device,
const mojom::blink::MediaStreamStateChange new_state) {
DVLOG(1) << __func__ << " label=" << label << " device_id=" << device.id;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = label_stream_map_.find(label);
if (it == label_stream_map_.end()) {
// This can happen if a user stops a device from JS at the same
// time as the underlying media device is unplugged from the system.
return;
}
Stream* stream = &it->value;
if (stream->on_device_request_state_change_cb)
stream->on_device_request_state_change_cb.Run(device, new_state);
}
void MediaStreamDeviceObserver::BindMediaStreamDeviceObserverReceiver(
mojo::PendingReceiver<mojom::blink::MediaStreamDeviceObserver> receiver) {
receiver_.reset();
receiver_.Bind(std::move(receiver));
}
void MediaStreamDeviceObserver::AddStream(
const String& label,
const blink::MediaStreamDevices& audio_devices,
const blink::MediaStreamDevices& video_devices,
WebMediaStreamDeviceObserver::OnDeviceStoppedCb on_device_stopped_cb,
WebMediaStreamDeviceObserver::OnDeviceChangedCb on_device_changed_cb,
WebMediaStreamDeviceObserver::OnDeviceRequestStateChangeCb
on_device_request_state_change_cb) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Stream stream;
stream.on_device_stopped_cb = std::move(on_device_stopped_cb);
stream.on_device_changed_cb = std::move(on_device_changed_cb);
stream.on_device_request_state_change_cb =
std::move(on_device_request_state_change_cb);
stream.audio_devices = audio_devices;
stream.video_devices = video_devices;
label_stream_map_.Set(label, stream);
}
void MediaStreamDeviceObserver::AddStream(const String& label,
const MediaStreamDevice& device) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
Stream stream;
if (IsAudioInputMediaType(device.type))
stream.audio_devices.push_back(device);
else if (IsVideoInputMediaType(device.type))
stream.video_devices.push_back(device);
else
NOTREACHED();
label_stream_map_.Set(label, stream);
}
bool MediaStreamDeviceObserver::RemoveStream(const String& label) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = label_stream_map_.find(label);
if (it == label_stream_map_.end())
return false;
label_stream_map_.erase(it);
return true;
}
void MediaStreamDeviceObserver::RemoveStreamDevice(
const MediaStreamDevice& device) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Remove |device| from all streams in |label_stream_map_|.
bool device_found = false;
Vector<String> streams_to_remove;
for (auto& entry : label_stream_map_) {
MediaStreamDevices& audio_devices = entry.value.audio_devices;
MediaStreamDevices& video_devices = entry.value.video_devices;
if (RemoveStreamDeviceFromArray(device, &audio_devices) ||
RemoveStreamDeviceFromArray(device, &video_devices)) {
device_found = true;
if (audio_devices.empty() && video_devices.empty())
streams_to_remove.push_back(entry.key);
}
}
DCHECK(device_found);
for (const String& label : streams_to_remove)
label_stream_map_.erase(label);
}
base::UnguessableToken MediaStreamDeviceObserver::GetAudioSessionId(
const String& label) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = label_stream_map_.find(label);
if (it == label_stream_map_.end() || it->value.audio_devices.empty())
return base::UnguessableToken();
return it->value.audio_devices[0].session_id();
}
base::UnguessableToken MediaStreamDeviceObserver::GetVideoSessionId(
const String& label) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = label_stream_map_.find(label);
if (it == label_stream_map_.end() || it->value.video_devices.empty())
return base::UnguessableToken();
return it->value.video_devices[0].session_id();
}
} // namespace blink