blob: 967b1fec9cbcd84dff195dbcdffba47f1362592b [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 "content/browser/renderer_host/media/media_stream_dispatcher_host.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/queue.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/system/system_monitor.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/media/audio_input_device_manager.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "content/browser/renderer_host/media/mock_video_capture_provider.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_system_impl.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/media_switches.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
#if defined(OS_CHROMEOS)
#include "chromeos/audio/cras_audio_handler.h"
#endif
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::SaveArg;
namespace content {
namespace {
constexpr int kProcessId = 5;
constexpr int kRenderId = 6;
constexpr int kRequesterId = 7;
constexpr int kPageRequestId = 8;
constexpr const char* kRegularVideoDeviceId = "stub_device_0";
constexpr const char* kDepthVideoDeviceId = "stub_device_1 (depth)";
constexpr media::VideoCaptureApi kStubCaptureApi =
media::VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE;
constexpr double kStubFocalLengthX = 135.0;
constexpr double kStubFocalLengthY = 135.6;
constexpr double kStubDepthNear = 0.0;
constexpr double kStubDepthFar = 65.535;
void AudioInputDevicesEnumerated(base::Closure quit_closure,
media::AudioDeviceDescriptions* out,
const MediaDeviceEnumeration& enumeration) {
for (const auto& info : enumeration[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT]) {
out->emplace_back(info.label, info.device_id, info.group_id);
}
std::move(quit_closure).Run();
}
} // anonymous namespace
class MockMediaStreamDispatcherHost
: public MediaStreamDispatcherHost,
public blink::mojom::MediaStreamDeviceObserver {
public:
MockMediaStreamDispatcherHost(int render_process_id,
int render_frame_id,
MediaStreamManager* manager)
: MediaStreamDispatcherHost(render_process_id, render_frame_id, manager),
task_runner_(base::ThreadTaskRunnerHandle::Get()),
binding_(this) {}
~MockMediaStreamDispatcherHost() override {}
// A list of mock methods.
MOCK_METHOD3(OnStreamGenerationSuccess,
void(int request_id,
int audio_array_size,
int video_array_size));
MOCK_METHOD2(OnStreamGenerationFailure,
void(int request_id, blink::MediaStreamRequestResult result));
MOCK_METHOD0(OnDeviceStopSuccess, void());
MOCK_METHOD0(OnDeviceOpenSuccess, void());
// Accessor to private functions.
void OnGenerateStream(int page_request_id,
const blink::StreamControls& controls,
const base::Closure& quit_closure) {
quit_closures_.push(quit_closure);
MediaStreamDispatcherHost::GenerateStream(
page_request_id, controls, false,
base::BindOnce(&MockMediaStreamDispatcherHost::OnStreamGenerated,
base::Unretained(this), page_request_id));
}
void OnStopStreamDevice(const std::string& device_id, int session_id) {
MediaStreamDispatcherHost::StopStreamDevice(device_id, session_id);
}
void OnOpenDevice(int page_request_id,
const std::string& device_id,
blink::MediaStreamType type,
const base::Closure& quit_closure) {
quit_closures_.push(quit_closure);
MediaStreamDispatcherHost::OpenDevice(
page_request_id, device_id, type,
base::BindOnce(&MockMediaStreamDispatcherHost::OnDeviceOpened,
base::Unretained(this)));
}
void OnStreamStarted(const std::string& label) override {
MediaStreamDispatcherHost::OnStreamStarted(label);
}
// mojom::MediaStreamDeviceObserver implementation.
void OnDeviceStopped(const std::string& label,
const blink::MediaStreamDevice& device) override {
OnDeviceStoppedInternal(label, device);
}
// mojom::MediaStreamDeviceObserver implementation.
void OnDeviceChanged(const std::string& label,
const blink::MediaStreamDevice& old_device,
const blink::MediaStreamDevice& new_device) override {}
blink::mojom::MediaStreamDeviceObserverPtr CreateInterfacePtrAndBind() {
blink::mojom::MediaStreamDeviceObserverPtr observer;
binding_.Bind(mojo::MakeRequest(&observer));
return observer;
}
std::string label_;
blink::MediaStreamDevices audio_devices_;
blink::MediaStreamDevices video_devices_;
blink::MediaStreamDevice opened_device_;
private:
// These handler methods do minimal things and delegate to the mock methods.
void OnStreamGenerated(int request_id,
blink::MediaStreamRequestResult result,
const std::string& label,
const blink::MediaStreamDevices& audio_devices,
const blink::MediaStreamDevices& video_devices) {
if (result != blink::MEDIA_DEVICE_OK) {
OnStreamGenerationFailed(request_id, result);
return;
}
OnStreamGenerationSuccess(request_id, audio_devices.size(),
video_devices.size());
// Simulate the stream started event back to host for UI testing.
OnStreamStarted(label);
// Notify that the event have occurred.
base::Closure quit_closure = quit_closures_.front();
quit_closures_.pop();
task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));
label_ = label;
audio_devices_ = audio_devices;
video_devices_ = video_devices;
}
void OnStreamGenerationFailed(int request_id,
blink::MediaStreamRequestResult result) {
OnStreamGenerationFailure(request_id, result);
if (!quit_closures_.empty()) {
base::Closure quit_closure = quit_closures_.front();
quit_closures_.pop();
task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));
}
label_.clear();
}
void OnDeviceStoppedInternal(const std::string& label,
const blink::MediaStreamDevice& device) {
if (IsVideoInputMediaType(device.type))
EXPECT_TRUE(device.IsSameDevice(video_devices_[0]));
if (IsAudioInputMediaType(device.type))
EXPECT_TRUE(device.IsSameDevice(audio_devices_[0]));
OnDeviceStopSuccess();
}
void OnDeviceOpened(bool success,
const std::string& label,
const blink::MediaStreamDevice& device) {
base::Closure quit_closure = quit_closures_.front();
quit_closures_.pop();
task_runner_->PostTask(FROM_HERE, base::ResetAndReturn(&quit_closure));
if (success) {
label_ = label;
opened_device_ = device;
OnDeviceOpenSuccess();
}
}
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
base::queue<base::Closure> quit_closures_;
mojo::Binding<blink::mojom::MediaStreamDeviceObserver> binding_;
};
class MockMediaStreamUIProxy : public FakeMediaStreamUIProxy {
public:
MockMediaStreamUIProxy()
: FakeMediaStreamUIProxy(/*tests_use_fake_render_frame_hosts=*/true) {}
void OnStarted(
base::OnceClosure stop,
base::RepeatingClosure source,
MediaStreamUIProxy::WindowIdCallback window_id_callback) override {
// gmock cannot handle move-only types:
MockOnStarted(base::AdaptCallbackForRepeating(std::move(stop)));
}
MOCK_METHOD1(MockOnStarted, void(base::Closure stop));
};
class MediaStreamDispatcherHostTest : public testing::Test {
public:
MediaStreamDispatcherHostTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP),
origin_(url::Origin::Create(GURL("https://test.com"))) {
audio_manager_ = std::make_unique<media::MockAudioManager>(
std::make_unique<media::TestAudioThread>());
audio_system_ =
std::make_unique<media::AudioSystemImpl>(audio_manager_.get());
browser_context_ = std::make_unique<TestBrowserContext>();
// Make sure we use fake devices to avoid long delays.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kUseFakeDeviceForMediaStream);
auto mock_video_capture_provider =
std::make_unique<MockVideoCaptureProvider>();
mock_video_capture_provider_ = mock_video_capture_provider.get();
// Create our own MediaStreamManager.
media_stream_manager_ = std::make_unique<MediaStreamManager>(
audio_system_.get(), audio_manager_->GetTaskRunner(),
std::move(mock_video_capture_provider));
host_ = std::make_unique<MockMediaStreamDispatcherHost>(
kProcessId, kRenderId, media_stream_manager_.get());
host_->set_salt_and_origin_callback_for_testing(
base::BindRepeating(&MediaStreamDispatcherHostTest::GetSaltAndOrigin,
base::Unretained(this)));
host_->SetMediaStreamDeviceObserverForTesting(
host_->CreateInterfacePtrAndBind());
#if defined(OS_CHROMEOS)
chromeos::CrasAudioHandler::InitializeForTesting();
#endif
}
~MediaStreamDispatcherHostTest() override {
audio_manager_->Shutdown();
#if defined(OS_CHROMEOS)
chromeos::CrasAudioHandler::Shutdown();
#endif
}
void SetUp() override {
stub_video_device_ids_.emplace_back(kRegularVideoDeviceId);
stub_video_device_ids_.emplace_back(kDepthVideoDeviceId);
ON_CALL(*mock_video_capture_provider_, DoGetDeviceInfosAsync(_))
.WillByDefault(Invoke(
[this](
VideoCaptureProvider::GetDeviceInfosCallback& result_callback) {
std::vector<media::VideoCaptureDeviceInfo> result;
for (const auto& device_id : stub_video_device_ids_) {
media::VideoCaptureDeviceInfo info;
info.descriptor.device_id = device_id;
info.descriptor.capture_api = kStubCaptureApi;
if (device_id == kDepthVideoDeviceId) {
info.descriptor.camera_calibration.emplace();
info.descriptor.camera_calibration->focal_length_x =
kStubFocalLengthX;
info.descriptor.camera_calibration->focal_length_y =
kStubFocalLengthY;
info.descriptor.camera_calibration->depth_near =
kStubDepthNear;
info.descriptor.camera_calibration->depth_far = kStubDepthFar;
}
result.push_back(info);
}
base::ResetAndReturn(&result_callback).Run(result);
}));
base::RunLoop run_loop;
MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
devices_to_enumerate[blink::MEDIA_DEVICE_TYPE_AUDIO_INPUT] = true;
media_stream_manager_->media_devices_manager()->EnumerateDevices(
devices_to_enumerate,
base::BindOnce(&AudioInputDevicesEnumerated, run_loop.QuitClosure(),
&audio_device_descriptions_));
run_loop.Run();
ASSERT_GT(audio_device_descriptions_.size(), 0u);
}
void TearDown() override { host_.reset(); }
MediaDeviceSaltAndOrigin GetSaltAndOrigin(int /* process_id */,
int /* frame_id */) {
return MediaDeviceSaltAndOrigin(browser_context_->GetMediaDeviceIDSalt(),
"fake_group_id_salt", origin_);
}
protected:
std::unique_ptr<FakeMediaStreamUIProxy> CreateMockUI(bool expect_started) {
std::unique_ptr<MockMediaStreamUIProxy> fake_ui =
std::make_unique<MockMediaStreamUIProxy>();
testing::Mock::AllowLeak(fake_ui.get());
if (expect_started)
EXPECT_CALL(*fake_ui, MockOnStarted(_));
return fake_ui;
}
virtual void SetupFakeUI(bool expect_started) {
media_stream_manager_->UseFakeUIFactoryForTests(
base::Bind(&MediaStreamDispatcherHostTest::CreateMockUI,
base::Unretained(this), expect_started));
}
void GenerateStreamAndWaitForResult(int page_request_id,
const blink::StreamControls& controls) {
base::RunLoop run_loop;
int expected_audio_array_size =
(controls.audio.requested && !audio_device_descriptions_.empty()) ? 1
: 0;
int expected_video_array_size =
(controls.video.requested && !stub_video_device_ids_.empty()) ? 1 : 0;
EXPECT_CALL(*host_, OnStreamGenerationSuccess(page_request_id,
expected_audio_array_size,
expected_video_array_size));
host_->OnGenerateStream(page_request_id, controls, run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(DoesContainRawIds(host_->audio_devices_));
EXPECT_FALSE(DoesContainRawIds(host_->video_devices_));
EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->audio_devices_, origin_));
EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->video_devices_, origin_));
}
void GenerateStreamAndWaitForFailure(
int page_request_id,
const blink::StreamControls& controls,
blink::MediaStreamRequestResult expected_result) {
base::RunLoop run_loop;
EXPECT_CALL(*host_,
OnStreamGenerationFailure(page_request_id, expected_result));
host_->OnGenerateStream(page_request_id, controls, run_loop.QuitClosure());
run_loop.Run();
}
void OpenVideoDeviceAndWaitForResult(int page_request_id,
const std::string& device_id) {
EXPECT_CALL(*host_, OnDeviceOpenSuccess());
base::RunLoop run_loop;
host_->OnOpenDevice(page_request_id, device_id,
blink::MEDIA_DEVICE_VIDEO_CAPTURE,
run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(DoesContainRawIds(host_->video_devices_));
EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->video_devices_, origin_));
}
void OpenVideoDeviceAndWaitForFailure(int page_request_id,
const std::string& device_id) {
EXPECT_CALL(*host_, OnDeviceOpenSuccess()).Times(0);
base::RunLoop run_loop;
host_->OnOpenDevice(page_request_id, device_id,
blink::MEDIA_DEVICE_VIDEO_CAPTURE,
run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(DoesContainRawIds(host_->video_devices_));
EXPECT_FALSE(DoesEveryDeviceMapToRawId(host_->video_devices_, origin_));
}
bool DoesContainRawIds(const blink::MediaStreamDevices& devices) {
for (size_t i = 0; i < devices.size(); ++i) {
if (devices[i].id != media::AudioDeviceDescription::kDefaultDeviceId &&
devices[i].id !=
media::AudioDeviceDescription::kCommunicationsDeviceId) {
for (const auto& audio_device : audio_device_descriptions_) {
if (audio_device.unique_id == devices[i].id)
return true;
}
}
for (const std::string& device_id : stub_video_device_ids_) {
if (device_id == devices[i].id)
return true;
}
}
return false;
}
bool DoesEveryDeviceMapToRawId(const blink::MediaStreamDevices& devices,
const url::Origin& origin) {
for (size_t i = 0; i < devices.size(); ++i) {
bool found_match = false;
media::AudioDeviceDescriptions::const_iterator audio_it =
audio_device_descriptions_.begin();
for (; audio_it != audio_device_descriptions_.end(); ++audio_it) {
if (DoesMediaDeviceIDMatchHMAC(browser_context_->GetMediaDeviceIDSalt(),
origin, devices[i].id,
audio_it->unique_id)) {
EXPECT_FALSE(found_match);
found_match = true;
}
}
for (const std::string& device_id : stub_video_device_ids_) {
if (DoesMediaDeviceIDMatchHMAC(browser_context_->GetMediaDeviceIDSalt(),
origin, devices[i].id, device_id)) {
EXPECT_FALSE(found_match);
found_match = true;
}
}
if (!found_match)
return false;
}
return true;
}
std::unique_ptr<MockMediaStreamDispatcherHost> host_;
std::unique_ptr<MediaStreamManager> media_stream_manager_;
TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<media::AudioManager> audio_manager_;
std::unique_ptr<media::AudioSystem> audio_system_;
std::unique_ptr<TestBrowserContext> browser_context_;
media::AudioDeviceDescriptions audio_device_descriptions_;
std::vector<std::string> stub_video_device_ids_;
url::Origin origin_;
MockVideoCaptureProvider* mock_video_capture_provider_;
};
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithVideoOnly) {
blink::StreamControls controls(false, true);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
}
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithAudioOnly) {
blink::StreamControls controls(true, false);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 1u);
EXPECT_EQ(host_->video_devices_.size(), 0u);
}
// This test simulates a shutdown scenario: we don't setup a fake UI proxy for
// MediaStreamManager, so it will create an ordinary one which will not find
// a RenderFrameHostDelegate. This normally should only be the case at shutdown.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithNothing) {
blink::StreamControls controls(false, false);
GenerateStreamAndWaitForFailure(kPageRequestId, controls,
blink::MEDIA_DEVICE_FAILED_DUE_TO_SHUTDOWN);
}
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithAudioAndVideo) {
blink::StreamControls controls(true, true);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 1u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
}
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamWithDepthVideo) {
// We specify to generate both audio and video stream.
blink::StreamControls controls(true, true);
std::string source_id = GetHMACForMediaDeviceID(
browser_context_->GetMediaDeviceIDSalt(), origin_, kDepthVideoDeviceId);
// |source_id| corresponds to the depth device. As we can generate only one
// video stream using GenerateStreamAndWaitForResult, we use
// controls.video.source_id to specify that the stream is depth video.
// See also MediaStreamManager::GenerateStream and other tests here.
controls.video.device_id = source_id;
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
// We specified the generation and expect to get
// one audio and one depth video stream.
EXPECT_EQ(host_->audio_devices_.size(), 1u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
// host_->video_devices_[0] contains the information about generated video
// stream device (the depth device).
const base::Optional<blink::CameraCalibration> calibration =
host_->video_devices_[0].camera_calibration;
EXPECT_TRUE(calibration);
EXPECT_EQ(calibration->focal_length_x, kStubFocalLengthX);
EXPECT_EQ(calibration->focal_length_y, kStubFocalLengthY);
EXPECT_EQ(calibration->depth_near, kStubDepthNear);
EXPECT_EQ(calibration->depth_far, kStubDepthFar);
}
// This test generates two streams with video only using the same render frame
// id. The same capture device with the same device and session id is expected
// to be used.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsFromSameRenderId) {
blink::StreamControls controls(false, true);
// Generate first stream.
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
// Check the latest generated stream.
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
const std::string label1 = host_->label_;
const std::string device_id1 = host_->video_devices_.front().id;
const int session_id1 = host_->video_devices_.front().session_id;
// Generate second stream.
GenerateStreamAndWaitForResult(kPageRequestId + 1, controls);
// Check the latest generated stream.
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
const std::string label2 = host_->label_;
const std::string device_id2 = host_->video_devices_.front().id;
int session_id2 = host_->video_devices_.front().session_id;
EXPECT_EQ(device_id1, device_id2);
EXPECT_EQ(session_id1, session_id2);
EXPECT_NE(label1, label2);
}
TEST_F(MediaStreamDispatcherHostTest,
GenerateStreamAndOpenDeviceFromSameRenderFrame) {
SetupFakeUI(true);
blink::StreamControls controls(false, true);
// Generate first stream.
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
const std::string label1 = host_->label_;
const std::string device_id1 = host_->video_devices_.front().id;
const int session_id1 = host_->video_devices_.front().session_id;
// Generate second stream.
OpenVideoDeviceAndWaitForResult(kPageRequestId, device_id1);
const std::string device_id2 = host_->opened_device_.id;
const int session_id2 = host_->opened_device_.session_id;
const std::string label2 = host_->label_;
EXPECT_EQ(device_id1, device_id2);
EXPECT_NE(session_id1, session_id2);
EXPECT_NE(label1, label2);
}
// This test generates two streams with video only using two separate render
// frame ids. The same device id but different session ids are expected.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsDifferentRenderId) {
blink::StreamControls controls(false, true);
// Generate first stream.
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
// Check the latest generated stream.
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
const std::string label1 = host_->label_;
const std::string device_id1 = host_->video_devices_.front().id;
const int session_id1 = host_->video_devices_.front().session_id;
// Generate second stream from another render frame.
host_ = std::make_unique<MockMediaStreamDispatcherHost>(
kProcessId, kRenderId + 1, media_stream_manager_.get());
host_->set_salt_and_origin_callback_for_testing(
base::BindRepeating(&MediaStreamDispatcherHostTest::GetSaltAndOrigin,
base::Unretained(this)));
host_->SetMediaStreamDeviceObserverForTesting(
host_->CreateInterfacePtrAndBind());
GenerateStreamAndWaitForResult(kPageRequestId + 1, controls);
// Check the latest generated stream.
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
const std::string label2 = host_->label_;
const std::string device_id2 = host_->video_devices_.front().id;
const int session_id2 = host_->video_devices_.front().session_id;
EXPECT_EQ(device_id1, device_id2);
EXPECT_NE(session_id1, session_id2);
EXPECT_NE(label1, label2);
}
// This test request two streams with video only without waiting for the first
// stream to be generated before requesting the second.
// The same device id and session ids are expected.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithoutWaiting) {
blink::StreamControls controls(false, true);
// Generate first stream.
SetupFakeUI(true);
{
InSequence s;
EXPECT_CALL(*host_, OnStreamGenerationSuccess(kPageRequestId, 0, 1));
// Generate second stream.
EXPECT_CALL(*host_, OnStreamGenerationSuccess(kPageRequestId + 1, 0, 1));
}
base::RunLoop run_loop1;
base::RunLoop run_loop2;
host_->OnGenerateStream(kPageRequestId, controls, run_loop1.QuitClosure());
host_->OnGenerateStream(kPageRequestId + 1, controls,
run_loop2.QuitClosure());
run_loop1.Run();
run_loop2.Run();
}
// Test that we can generate streams where a sourceId is specified in
// the request.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithSourceId) {
ASSERT_GE(audio_device_descriptions_.size(), 1u);
ASSERT_GE(stub_video_device_ids_.size(), 1u);
media::AudioDeviceDescriptions::const_iterator audio_it =
audio_device_descriptions_.begin();
for (; audio_it != audio_device_descriptions_.end(); ++audio_it) {
std::string source_id = GetHMACForMediaDeviceID(
browser_context_->GetMediaDeviceIDSalt(), origin_, audio_it->unique_id);
ASSERT_FALSE(source_id.empty());
blink::StreamControls controls(true, true);
controls.audio.device_id = source_id;
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_[0].id, source_id);
}
for (const std::string& device_id : stub_video_device_ids_) {
std::string source_id = GetHMACForMediaDeviceID(
browser_context_->GetMediaDeviceIDSalt(), origin_, device_id);
ASSERT_FALSE(source_id.empty());
blink::StreamControls controls(true, true);
controls.video.device_id = source_id;
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->video_devices_[0].id, source_id);
}
}
// Test that generating a stream with an invalid video source id fail.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithInvalidVideoSourceId) {
blink::StreamControls controls(true, true);
controls.video.device_id = "invalid source id";
GenerateStreamAndWaitForFailure(kPageRequestId, controls,
blink::MEDIA_DEVICE_NO_HARDWARE);
}
// Test that generating a stream with an invalid audio source id fail.
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsWithInvalidAudioSourceId) {
blink::StreamControls controls(true, true);
controls.audio.device_id = "invalid source id";
GenerateStreamAndWaitForFailure(kPageRequestId, controls,
blink::MEDIA_DEVICE_NO_HARDWARE);
}
TEST_F(MediaStreamDispatcherHostTest, GenerateStreamsNoAvailableVideoDevice) {
stub_video_device_ids_.clear();
blink::StreamControls controls(true, true);
SetupFakeUI(false);
GenerateStreamAndWaitForFailure(kPageRequestId, controls,
blink::MEDIA_DEVICE_NO_HARDWARE);
}
// Test that if a OnStopStreamDevice message is received for a device that has
// been opened in a MediaStream and by pepper, the device is only stopped for
// the MediaStream.
TEST_F(MediaStreamDispatcherHostTest, StopDeviceInStream) {
blink::StreamControls controls(false, true);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
std::string stream_request_label = host_->label_;
blink::MediaStreamDevice video_device = host_->video_devices_.front();
ASSERT_EQ(
1u, media_stream_manager_->GetDevicesOpenedByRequest(stream_request_label)
.size());
// Open the same device by Pepper.
OpenVideoDeviceAndWaitForResult(kPageRequestId, video_device.id);
std::string open_device_request_label = host_->label_;
// Stop the device in the MediaStream.
host_->OnStopStreamDevice(video_device.id, video_device.session_id);
EXPECT_EQ(
0u, media_stream_manager_->GetDevicesOpenedByRequest(stream_request_label)
.size());
EXPECT_EQ(1u, media_stream_manager_
->GetDevicesOpenedByRequest(open_device_request_label)
.size());
}
TEST_F(MediaStreamDispatcherHostTest, StopDeviceInStreamAndRestart) {
blink::StreamControls controls(true, true);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
std::string request_label1 = host_->label_;
blink::MediaStreamDevice video_device = host_->video_devices_.front();
// Expect that 1 audio and 1 video device has been opened.
EXPECT_EQ(
2u,
media_stream_manager_->GetDevicesOpenedByRequest(request_label1).size());
host_->OnStopStreamDevice(video_device.id, video_device.session_id);
EXPECT_EQ(
1u,
media_stream_manager_->GetDevicesOpenedByRequest(request_label1).size());
GenerateStreamAndWaitForResult(kPageRequestId, controls);
std::string request_label2 = host_->label_;
blink::MediaStreamDevices request1_devices =
media_stream_manager_->GetDevicesOpenedByRequest(request_label1);
blink::MediaStreamDevices request2_devices =
media_stream_manager_->GetDevicesOpenedByRequest(request_label2);
ASSERT_EQ(1u, request1_devices.size());
ASSERT_EQ(2u, request2_devices.size());
// Test that the same audio device has been opened in both streams.
EXPECT_TRUE(request1_devices[0].IsSameDevice(request2_devices[0]) ||
request1_devices[0].IsSameDevice(request2_devices[1]));
}
TEST_F(MediaStreamDispatcherHostTest,
GenerateTwoStreamsAndStopDeviceWhileWaitingForSecondStream) {
blink::StreamControls controls(false, true);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->video_devices_.size(), 1u);
// Generate a second stream.
EXPECT_CALL(*host_, OnStreamGenerationSuccess(kPageRequestId + 1, 0, 1));
base::RunLoop run_loop1;
host_->OnGenerateStream(kPageRequestId + 1, controls,
run_loop1.QuitClosure());
// Stop the video stream device from stream 1 while waiting for the
// second stream to be generated.
host_->OnStopStreamDevice(host_->video_devices_[0].id,
host_->video_devices_[0].session_id);
run_loop1.Run();
EXPECT_EQ(host_->video_devices_.size(), 1u);
}
TEST_F(MediaStreamDispatcherHostTest, CancelPendingStreams) {
blink::StreamControls controls(false, true);
base::RunLoop run_loop;
// Create multiple GenerateStream requests.
size_t streams = 5;
for (size_t i = 1; i <= streams; ++i) {
host_->OnGenerateStream(kPageRequestId + i, controls,
run_loop.QuitClosure());
}
media_stream_manager_->CancelAllRequests(kProcessId, kRenderId, kRequesterId);
run_loop.RunUntilIdle();
}
TEST_F(MediaStreamDispatcherHostTest, StopGeneratedStreams) {
blink::StreamControls controls(false, true);
SetupFakeUI(true);
// Create first group of streams.
size_t generated_streams = 3;
for (size_t i = 0; i < generated_streams; ++i)
GenerateStreamAndWaitForResult(kPageRequestId + i, controls);
media_stream_manager_->CancelAllRequests(kProcessId, kRenderId, kRequesterId);
base::RunLoop().RunUntilIdle();
}
TEST_F(MediaStreamDispatcherHostTest, CloseFromUI) {
blink::StreamControls controls(false, true);
base::Closure close_callback;
media_stream_manager_->UseFakeUIFactoryForTests(base::Bind(
[](base::Closure* close_callback) {
std::unique_ptr<FakeMediaStreamUIProxy> stream_ui =
std::make_unique<MockMediaStreamUIProxy>();
EXPECT_CALL(*static_cast<MockMediaStreamUIProxy*>(stream_ui.get()),
MockOnStarted(_))
.WillOnce(SaveArg<0>(close_callback));
return stream_ui;
},
&close_callback));
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
ASSERT_FALSE(close_callback.is_null());
EXPECT_CALL(*host_, OnDeviceStopSuccess());
close_callback.Run();
base::RunLoop().RunUntilIdle();
}
// Test that the observer is notified if a video device that is in use is
// being unplugged.
TEST_F(MediaStreamDispatcherHostTest, VideoDeviceUnplugged) {
blink::StreamControls controls(true, true);
SetupFakeUI(true);
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 1u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
stub_video_device_ids_.clear();
base::RunLoop run_loop;
EXPECT_CALL(*host_, OnDeviceStopSuccess())
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
media_stream_manager_->media_devices_manager()->OnDevicesChanged(
base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE);
run_loop.Run();
}
// Test that changing the salt invalidates device IDs. Attempts to open an
// invalid device ID result in failure.
TEST_F(MediaStreamDispatcherHostTest, Salt) {
SetupFakeUI(true);
blink::StreamControls controls(false, true);
// Generate first stream.
GenerateStreamAndWaitForResult(kPageRequestId, controls);
EXPECT_EQ(host_->audio_devices_.size(), 0u);
EXPECT_EQ(host_->video_devices_.size(), 1u);
const std::string label1 = host_->label_;
const std::string device_id1 = host_->video_devices_.front().id;
EXPECT_TRUE(host_->video_devices_.front().group_id.has_value());
const std::string group_id1 = *host_->video_devices_.front().group_id;
EXPECT_FALSE(group_id1.empty());
const int session_id1 = host_->video_devices_.front().session_id;
// Generate second stream.
OpenVideoDeviceAndWaitForResult(kPageRequestId, device_id1);
const std::string device_id2 = host_->opened_device_.id;
EXPECT_TRUE(host_->opened_device_.group_id.has_value());
const std::string group_id2 = *host_->opened_device_.group_id;
EXPECT_FALSE(group_id2.empty());
const int session_id2 = host_->opened_device_.session_id;
const std::string label2 = host_->label_;
EXPECT_EQ(device_id1, device_id2);
EXPECT_EQ(group_id1, group_id2);
EXPECT_NE(session_id1, session_id2);
EXPECT_NE(label1, label2);
// Reset salt and try to generate third stream with the invalidated device ID.
browser_context_ = std::make_unique<TestBrowserContext>();
EXPECT_CALL(*host_, OnDeviceOpenSuccess()).Times(0);
OpenVideoDeviceAndWaitForFailure(kPageRequestId, device_id1);
// Last open device ID and session are from the second stream.
EXPECT_EQ(session_id2, host_->opened_device_.session_id);
EXPECT_EQ(device_id2, host_->opened_device_.id);
EXPECT_EQ(group_id2, host_->opened_device_.group_id);
}
}; // namespace content