Commit 6ea29c73 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media Image] Add controller image API

Add a MediaControllerImageObserver that can observe
images of a certain size and type on a media
controller. When the controller receives new images
from the session it will handle the rank and
retrieval so the client does not have to.

BUG=914430

Change-Id: Ib6d0fc0b232efc790f3018b6687d969deb7111cd
Reviewed-on: https://chromium-review.googlesource.com/c/1464649Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#635574}
parent ef342abb
...@@ -4,8 +4,79 @@ ...@@ -4,8 +4,79 @@
#include "services/media_session/media_controller.h" #include "services/media_session/media_controller.h"
#include <set>
namespace media_session { namespace media_session {
// ImageObserverHolder will hold each mojo image observer with the image
// size and type preferences it specified when the observer was added.
class MediaController::ImageObserverHolder {
public:
ImageObserverHolder(MediaController* owner,
mojom::MediaSessionImageType type,
int minimum_size_px,
int desired_size_px,
mojom::MediaControllerImageObserverPtr observer,
const std::vector<MediaImage>& current_images)
: owner_(owner),
type_(type),
minimum_size_px_(minimum_size_px),
desired_size_px_(desired_size_px),
observer_(std::move(observer)) {
// Set a connection error handler so that we will remove observers that have
// had an error / been closed.
observer_.set_connection_error_handler(base::BindOnce(
&MediaController::CleanupImageObservers, base::Unretained(owner_)));
// Flush the observer with the latest state.
ImagesChanged(current_images);
}
~ImageObserverHolder() = default;
bool is_valid() const { return !observer_.encountered_error(); }
mojom::MediaSessionImageType type() const { return type_; }
void ImagesChanged(const std::vector<MediaImage>& images) {
// If we do not have any images then we should call with an empty image to
// flush the observer.
if (images.empty()) {
ClearImage();
return;
}
// TODO(beccahughes): Pick the best image based on ranking.
DCHECK(owner_->session_);
owner_->session_->GetMediaImageBitmap(
images.at(0), minimum_size_px_, desired_size_px_,
base::BindOnce(&MediaController::ImageObserverHolder::OnImage,
base::Unretained(this)));
}
void ClearImage() {
observer_->MediaControllerImageChanged(type_, SkBitmap());
}
private:
void OnImage(const SkBitmap& image) {
observer_->MediaControllerImageChanged(type_, image);
}
MediaController* const owner_;
mojom::MediaSessionImageType const type_;
int const minimum_size_px_;
int const desired_size_px_;
mojom::MediaControllerImageObserverPtr observer_;
DISALLOW_COPY_AND_ASSIGN(ImageObserverHolder);
};
MediaController::MediaController() { MediaController::MediaController() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
} }
...@@ -95,7 +166,31 @@ void MediaController::MediaSessionActionsChanged( ...@@ -95,7 +166,31 @@ void MediaController::MediaSessionActionsChanged(
void MediaController::MediaSessionImagesChanged( void MediaController::MediaSessionImagesChanged(
const base::flat_map<mojom::MediaSessionImageType, std::vector<MediaImage>>& const base::flat_map<mojom::MediaSessionImageType, std::vector<MediaImage>>&
images) { images) {
// TODO(beccahughes): Implement this DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Work out which image types have changed.
std::set<mojom::MediaSessionImageType> types_changed;
for (const auto& entry : images) {
auto it = session_images_.find(entry.first);
if (it != session_images_.end() && entry.second == it->second)
continue;
types_changed.insert(entry.first);
}
session_images_ = images;
for (auto& holder : image_observers_) {
auto it = session_images_.find(holder->type());
if (it == session_images_.end()) {
// No image of this type is available from the session so we should clear
// any image the observers might have.
holder->ClearImage();
} else if (base::ContainsKey(types_changed, holder->type())) {
holder->ImagesChanged(it->second);
}
}
} }
void MediaController::PreviousTrack() { void MediaController::PreviousTrack() {
...@@ -119,6 +214,20 @@ void MediaController::Seek(base::TimeDelta seek_time) { ...@@ -119,6 +214,20 @@ void MediaController::Seek(base::TimeDelta seek_time) {
session_->Seek(seek_time); session_->Seek(seek_time);
} }
void MediaController::ObserveImages(
mojom::MediaSessionImageType type,
int minimum_size_px,
int desired_size_px,
mojom::MediaControllerImageObserverPtr observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = session_images_.find(type);
image_observers_.push_back(std::make_unique<ImageObserverHolder>(
this, type, minimum_size_px, desired_size_px, std::move(observer),
it == session_images_.end() ? std::vector<MediaImage>() : it->second));
}
bool MediaController::SetMediaSession(mojom::MediaSession* session) { bool MediaController::SetMediaSession(mojom::MediaSession* session) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
...@@ -129,6 +238,7 @@ bool MediaController::SetMediaSession(mojom::MediaSession* session) { ...@@ -129,6 +238,7 @@ bool MediaController::SetMediaSession(mojom::MediaSession* session) {
session_info_.reset(); session_info_.reset();
session_metadata_.reset(); session_metadata_.reset();
session_actions_.clear(); session_actions_.clear();
session_images_.clear();
if (session) { if (session) {
// Add |this| as an observer for |session|. // Add |this| as an observer for |session|.
...@@ -144,6 +254,9 @@ bool MediaController::SetMediaSession(mojom::MediaSession* session) { ...@@ -144,6 +254,9 @@ bool MediaController::SetMediaSession(mojom::MediaSession* session) {
observer->MediaSessionActionsChanged( observer->MediaSessionActionsChanged(
std::vector<mojom::MediaSessionAction>()); std::vector<mojom::MediaSessionAction>());
}); });
for (auto& holder : image_observers_)
holder->ClearImage();
} }
} }
...@@ -160,4 +273,9 @@ void MediaController::FlushForTesting() { ...@@ -160,4 +273,9 @@ void MediaController::FlushForTesting() {
bindings_.FlushForTesting(); bindings_.FlushForTesting();
} }
void MediaController::CleanupImageObservers() {
base::EraseIf(image_observers_,
[](const auto& holder) { return !holder->is_valid(); });
}
} // namespace media_session } // namespace media_session
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
#define SERVICES_MEDIA_SESSION_MEDIA_CONTROLLER_H_ #define SERVICES_MEDIA_SESSION_MEDIA_CONTROLLER_H_
#include <memory> #include <memory>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
...@@ -37,6 +40,10 @@ class MediaController : public mojom::MediaController, ...@@ -37,6 +40,10 @@ class MediaController : public mojom::MediaController,
void PreviousTrack() override; void PreviousTrack() override;
void NextTrack() override; void NextTrack() override;
void Seek(base::TimeDelta seek_time) override; void Seek(base::TimeDelta seek_time) override;
void ObserveImages(mojom::MediaSessionImageType type,
int minimum_size_px,
int desired_size_px,
mojom::MediaControllerImageObserverPtr observer) override;
// mojom::MediaSessionObserver overrides. // mojom::MediaSessionObserver overrides.
void MediaSessionInfoChanged( void MediaSessionInfoChanged(
...@@ -57,6 +64,13 @@ class MediaController : public mojom::MediaController, ...@@ -57,6 +64,13 @@ class MediaController : public mojom::MediaController,
void FlushForTesting(); void FlushForTesting();
private: private:
friend class MediaControllerTest;
class ImageObserverHolder;
// Removes unbound or faulty image observers.
void CleanupImageObservers();
// Holds mojo bindings for mojom::MediaController. // Holds mojo bindings for mojom::MediaController.
mojo::BindingSet<mojom::MediaController> bindings_; mojo::BindingSet<mojom::MediaController> bindings_;
...@@ -69,6 +83,10 @@ class MediaController : public mojom::MediaController, ...@@ -69,6 +83,10 @@ class MediaController : public mojom::MediaController,
// The current actions for |session_|. // The current actions for |session_|.
std::vector<mojom::MediaSessionAction> session_actions_; std::vector<mojom::MediaSessionAction> session_actions_;
// The current images for |session_|.
base::flat_map<mojom::MediaSessionImageType, std::vector<MediaImage>>
session_images_;
// Raw pointer to the local proxy. This is used for sending control events to // Raw pointer to the local proxy. This is used for sending control events to
// the underlying MediaSession. // the underlying MediaSession.
mojom::MediaSession* session_ = nullptr; mojom::MediaSession* session_ = nullptr;
...@@ -79,6 +97,9 @@ class MediaController : public mojom::MediaController, ...@@ -79,6 +97,9 @@ class MediaController : public mojom::MediaController,
// Binding for |this| to act as an observer to |session_|. // Binding for |this| to act as an observer to |session_|.
mojo::Binding<mojom::MediaSessionObserver> session_binding_{this}; mojo::Binding<mojom::MediaSessionObserver> session_binding_{this};
// Manages individual image observers.
std::vector<std::unique_ptr<ImageObserverHolder>> image_observers_;
// Protects |session_| as it is not thread safe. // Protects |session_| as it is not thread safe.
SEQUENCE_CHECKER(sequence_checker_); SEQUENCE_CHECKER(sequence_checker_);
......
...@@ -61,6 +61,10 @@ class MediaControllerTest : public testing::Test { ...@@ -61,6 +61,10 @@ class MediaControllerTest : public testing::Test {
return controller_manager_ptr_; return controller_manager_ptr_;
} }
static size_t GetImageObserverCount(const MediaController& controller) {
return controller.image_observers_.size();
}
private: private:
base::test::ScopedTaskEnvironment task_environment_; base::test::ScopedTaskEnvironment task_environment_;
service_manager::TestConnectorFactory connector_factory_; service_manager::TestConnectorFactory connector_factory_;
...@@ -869,4 +873,136 @@ TEST_F(MediaControllerTest, ActiveController_AddObserver_Abandoned) { ...@@ -869,4 +873,136 @@ TEST_F(MediaControllerTest, ActiveController_AddObserver_Abandoned) {
} }
} }
TEST_F(MediaControllerTest, ClearImageObserverOnError) {
MediaController controller;
mojom::MediaControllerPtr controller_ptr;
controller.BindToInterface(mojo::MakeRequest(&controller_ptr));
EXPECT_EQ(0u, GetImageObserverCount(controller));
{
test::TestMediaControllerImageObserver observer(controller_ptr);
EXPECT_EQ(1u, GetImageObserverCount(controller));
}
EXPECT_EQ(1u, GetImageObserverCount(controller));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, GetImageObserverCount(controller));
}
TEST_F(MediaControllerTest, ActiveController_SimulateImagesChanged) {
test::MockMediaSession media_session;
media_session.SetIsControllable(true);
{
test::MockMediaSessionMojoObserver observer(media_session);
RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
}
std::vector<MediaImage> images;
MediaImage image;
image.src = GURL("https://www.google.com");
images.push_back(image);
{
test::TestMediaControllerImageObserver observer(controller());
// By default, we should receive an empty image.
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
true);
EXPECT_TRUE(media_session.last_image_src().is_empty());
// Check that we receive the correct image and that it was requested from
// |media_session| by the controller.
media_session.SetImagesOfType(mojom::MediaSessionImageType::kArtwork,
images);
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
false);
EXPECT_EQ(image.src, media_session.last_image_src());
// Check that we flush the observer with an empty image. Since the image is
// empty the last downloaded image by |media_session| should still be the
// previous image.
media_session.SetImagesOfType(mojom::MediaSessionImageType::kArtwork,
std::vector<MediaImage>());
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
true);
EXPECT_EQ(image.src, media_session.last_image_src());
}
}
TEST_F(MediaControllerTest,
ActiveController_SimulateImagesChanged_ToggleControllable) {
test::MockMediaSession media_session;
media_session.SetIsControllable(true);
{
test::MockMediaSessionMojoObserver observer(media_session);
RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
}
std::vector<MediaImage> images;
MediaImage image;
image.src = GURL("https://www.google.com");
images.push_back(image);
media_session.SetImagesOfType(mojom::MediaSessionImageType::kArtwork, images);
{
test::TestMediaControllerImageObserver observer(controller());
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
false);
EXPECT_EQ(image.src, media_session.last_image_src());
// When the |media_session| becomes uncontrollable it is unbound from the
// media controller and we should flush the observer with an empty image.
media_session.SetIsControllable(false);
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
true);
// When the |media_session| becomes controllable again it will be bound to
// the media controller and we should flush the observer with the current
// images.
media_session.SetIsControllable(true);
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
false);
EXPECT_EQ(image.src, media_session.last_image_src());
}
}
TEST_F(MediaControllerTest,
ActiveController_SimulateImagesChanged_TypeChanged) {
test::MockMediaSession media_session;
media_session.SetIsControllable(true);
{
test::MockMediaSessionMojoObserver observer(media_session);
RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
}
std::vector<MediaImage> images;
MediaImage image;
image.src = GURL("https://www.google.com");
images.push_back(image);
media_session.SetImagesOfType(mojom::MediaSessionImageType::kArtwork, images);
{
test::TestMediaControllerImageObserver observer(controller());
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
false);
EXPECT_EQ(image.src, media_session.last_image_src());
// If we clear all the images associated with the media session we should
// flush all the observers.
media_session.ClearAllImages();
observer.WaitForExpectedImageOfType(mojom::MediaSessionImageType::kArtwork,
true);
EXPECT_EQ(image.src, media_session.last_image_src());
}
}
} // namespace media_session } // namespace media_session
...@@ -70,8 +70,8 @@ bool StructTraits<media_session::mojom::MediaImageBitmapDataView, SkBitmap>:: ...@@ -70,8 +70,8 @@ bool StructTraits<media_session::mojom::MediaImageBitmapDataView, SkBitmap>::
return false; return false;
} }
// Create the SkBitmap object which wraps the arc bitmap pixels. This // Create the SkBitmap object which wraps the media image bitmap pixels.
// doesn't copy and |data| and |bitmap| share the buffer. // This doesn't copy and |data| and |bitmap| share the buffer.
SkBitmap bitmap; SkBitmap bitmap;
if (!bitmap.installPixels(info, const_cast<uint8_t*>(pixel_data.data()), if (!bitmap.installPixels(info, const_cast<uint8_t*>(pixel_data.data()),
info.minRowBytes())) { info.minRowBytes())) {
...@@ -79,10 +79,15 @@ bool StructTraits<media_session::mojom::MediaImageBitmapDataView, SkBitmap>:: ...@@ -79,10 +79,15 @@ bool StructTraits<media_session::mojom::MediaImageBitmapDataView, SkBitmap>::
return false; return false;
} }
// Copy the pixels with converting color type. // Copy the pixels into |out|.
SkImageInfo image_info = info.makeColorType(kN32_SkColorType); return out->tryAllocPixels(info) &&
return out->tryAllocPixels(image_info) && bitmap.readPixels(info, out->getPixels(), out->rowBytes(), 0, 0);
bitmap.readPixels(image_info, out->getPixels(), out->rowBytes(), 0, 0); }
// static
void StructTraits<media_session::mojom::MediaImageBitmapDataView,
SkBitmap>::SetToNull(SkBitmap* out) {
out->reset();
} }
} // namespace mojo } // namespace mojo
...@@ -70,6 +70,7 @@ struct StructTraits<media_session::mojom::MediaImageBitmapDataView, SkBitmap> { ...@@ -70,6 +70,7 @@ struct StructTraits<media_session::mojom::MediaImageBitmapDataView, SkBitmap> {
SkBitmap* out); SkBitmap* out);
static bool IsNull(const SkBitmap& r) { return r.isNull(); } static bool IsNull(const SkBitmap& r) { return r.isNull(); }
static void SetToNull(SkBitmap* out);
}; };
} // namespace mojo } // namespace mojo
......
...@@ -192,6 +192,7 @@ void MockMediaSession::AddObserver(mojom::MediaSessionObserverPtr observer) { ...@@ -192,6 +192,7 @@ void MockMediaSession::AddObserver(mojom::MediaSessionObserverPtr observer) {
std::vector<mojom::MediaSessionAction> actions(actions_.begin(), std::vector<mojom::MediaSessionAction> actions(actions_.begin(),
actions_.end()); actions_.end());
observer->MediaSessionActionsChanged(actions); observer->MediaSessionActionsChanged(actions);
observer->MediaSessionImagesChanged(images_);
observers_.AddPtr(std::move(observer)); observers_.AddPtr(std::move(observer));
} }
...@@ -223,6 +224,20 @@ void MockMediaSession::Stop(SuspendType type) { ...@@ -223,6 +224,20 @@ void MockMediaSession::Stop(SuspendType type) {
SetState(mojom::MediaSessionInfo::SessionState::kInactive); SetState(mojom::MediaSessionInfo::SessionState::kInactive);
} }
void MockMediaSession::GetMediaImageBitmap(
const MediaImage& image,
int minimum_size_px,
int desired_size_px,
GetMediaImageBitmapCallback callback) {
last_image_src_ = image.src;
SkBitmap bitmap;
bitmap.allocPixels(
SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kOpaque_SkAlphaType));
std::move(callback).Run(bitmap);
}
void MockMediaSession::SetIsControllable(bool value) { void MockMediaSession::SetIsControllable(bool value) {
is_controllable_ = value; is_controllable_ = value;
NotifyObservers(); NotifyObservers();
...@@ -332,6 +347,23 @@ void MockMediaSession::SimulateMetadataChanged( ...@@ -332,6 +347,23 @@ void MockMediaSession::SimulateMetadataChanged(
}); });
} }
void MockMediaSession::ClearAllImages() {
images_.clear();
observers_.ForAllPtrs([this](mojom::MediaSessionObserver* observer) {
observer->MediaSessionImagesChanged(this->images_);
});
}
void MockMediaSession::SetImagesOfType(mojom::MediaSessionImageType type,
const std::vector<MediaImage>& images) {
images_.insert_or_assign(type, images);
observers_.ForAllPtrs([this](mojom::MediaSessionObserver* observer) {
observer->MediaSessionImagesChanged(this->images_);
});
}
void MockMediaSession::EnableAction(mojom::MediaSessionAction action) { void MockMediaSession::EnableAction(mojom::MediaSessionAction action) {
if (base::ContainsKey(actions_, action)) if (base::ContainsKey(actions_, action))
return; return;
......
...@@ -123,7 +123,7 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -123,7 +123,7 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
void GetMediaImageBitmap(const MediaImage& image, void GetMediaImageBitmap(const MediaImage& image,
int minimum_size_px, int minimum_size_px,
int desired_size_px, int desired_size_px,
GetMediaImageBitmapCallback callback) override {} GetMediaImageBitmapCallback callback) override;
void SetIsControllable(bool value); void SetIsControllable(bool value);
void SetPreferStop(bool value) { prefer_stop_ = value; } void SetPreferStop(bool value) { prefer_stop_ = value; }
...@@ -149,6 +149,10 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -149,6 +149,10 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
void SimulateMetadataChanged(const base::Optional<MediaMetadata>& metadata); void SimulateMetadataChanged(const base::Optional<MediaMetadata>& metadata);
void ClearAllImages();
void SetImagesOfType(mojom::MediaSessionImageType type,
const std::vector<MediaImage>& images);
void EnableAction(mojom::MediaSessionAction action); void EnableAction(mojom::MediaSessionAction action);
void DisableAction(mojom::MediaSessionAction action); void DisableAction(mojom::MediaSessionAction action);
...@@ -157,6 +161,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -157,6 +161,8 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
int add_observer_count() const { return add_observer_count_; } int add_observer_count() const { return add_observer_count_; }
int seek_count() const { return seek_count_; } int seek_count() const { return seek_count_; }
const GURL& last_image_src() const { return last_image_src_; }
private: private:
void SetState(mojom::MediaSessionInfo::SessionState); void SetState(mojom::MediaSessionInfo::SessionState);
void NotifyObservers(); void NotifyObservers();
...@@ -180,6 +186,9 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession ...@@ -180,6 +186,9 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) MockMediaSession
mojom::MediaSessionInfo::SessionState state_ = mojom::MediaSessionInfo::SessionState state_ =
mojom::MediaSessionInfo::SessionState::kInactive; mojom::MediaSessionInfo::SessionState::kInactive;
base::flat_map<mojom::MediaSessionImageType, std::vector<MediaImage>> images_;
GURL last_image_src_;
mojo::BindingSet<mojom::MediaSession> bindings_; mojo::BindingSet<mojom::MediaSession> bindings_;
mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_; mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_;
......
...@@ -7,6 +7,47 @@ ...@@ -7,6 +7,47 @@
namespace media_session { namespace media_session {
namespace test { namespace test {
TestMediaControllerImageObserver::TestMediaControllerImageObserver(
mojom::MediaControllerPtr& controller) {
mojom::MediaControllerImageObserverPtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
controller->ObserveImages(mojom::MediaSessionImageType::kArtwork, 0, 0,
std::move(ptr));
controller.FlushForTesting();
}
TestMediaControllerImageObserver::~TestMediaControllerImageObserver() = default;
void TestMediaControllerImageObserver::MediaControllerImageChanged(
mojom::MediaSessionImageType type,
const SkBitmap& bitmap) {
current_ = ImageTypePair(type, bitmap.isNull());
if (!expected_.has_value() || expected_ != current_)
return;
DCHECK(run_loop_);
run_loop_->Quit();
expected_.reset();
}
void TestMediaControllerImageObserver::WaitForExpectedImageOfType(
mojom::MediaSessionImageType type,
bool expect_null_image) {
ImageTypePair pair(type, expect_null_image);
if (current_ == pair)
return;
expected_ = pair;
DCHECK(!run_loop_);
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
run_loop_.reset();
}
TestMediaControllerObserver::TestMediaControllerObserver( TestMediaControllerObserver::TestMediaControllerObserver(
mojom::MediaControllerPtr& media_controller) mojom::MediaControllerPtr& media_controller)
: binding_(this) { : binding_(this) {
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_TEST_MEDIA_CONTROLLER_H_ #ifndef SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_TEST_MEDIA_CONTROLLER_H_
#define SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_TEST_MEDIA_CONTROLLER_H_ #define SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_TEST_MEDIA_CONTROLLER_H_
#include <utility>
#include "base/component_export.h" #include "base/component_export.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding.h"
...@@ -15,6 +17,34 @@ ...@@ -15,6 +17,34 @@
namespace media_session { namespace media_session {
namespace test { namespace test {
// A mock MediaControllerImageObserver than can be used for waiting for images.
class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP)
TestMediaControllerImageObserver
: public mojom::MediaControllerImageObserver {
public:
explicit TestMediaControllerImageObserver(
mojom::MediaControllerPtr& controller);
~TestMediaControllerImageObserver() override;
// mojom::MediaControllerImageObserver overrides.
void MediaControllerImageChanged(mojom::MediaSessionImageType type,
const SkBitmap& bitmap) override;
void WaitForExpectedImageOfType(mojom::MediaSessionImageType type,
bool expect_null_value);
private:
// The bool is whether the image type should be a null value.
using ImageTypePair = std::pair<mojom::MediaSessionImageType, bool>;
std::unique_ptr<base::RunLoop> run_loop_;
base::Optional<ImageTypePair> expected_;
base::Optional<ImageTypePair> current_;
mojo::Binding<mojom::MediaControllerImageObserver> binding_{this};
};
// A mock MediaControllerObsever that can be used for waiting for state changes. // A mock MediaControllerObsever that can be used for waiting for state changes.
class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP)
TestMediaControllerObserver : public mojom::MediaControllerObserver { TestMediaControllerObserver : public mojom::MediaControllerObserver {
...@@ -93,6 +123,11 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestMediaController ...@@ -93,6 +123,11 @@ class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestMediaController
void PreviousTrack() override; void PreviousTrack() override;
void NextTrack() override; void NextTrack() override;
void Seek(base::TimeDelta seek_time) override; void Seek(base::TimeDelta seek_time) override;
void ObserveImages(mojom::MediaSessionImageType type,
int minimum_size_px,
int desired_size_px,
mojom::MediaControllerImageObserverPtr observer) override {
}
int toggle_suspend_resume_count() const { int toggle_suspend_resume_count() const {
return toggle_suspend_resume_count_; return toggle_suspend_resume_count_;
......
...@@ -56,6 +56,13 @@ interface MediaController { ...@@ -56,6 +56,13 @@ interface MediaController {
// The |kDefaultSeekTimeSeconds| provides a default value for seeking by a // The |kDefaultSeekTimeSeconds| provides a default value for seeking by a
// few seconds. // few seconds.
Seek(mojo_base.mojom.TimeDelta seek_time); Seek(mojo_base.mojom.TimeDelta seek_time);
// Creates an image observer that will be notified when the image of |type|
// for the underlying media session has changed. The image will be at least
// |minimum_size_pc| and closest to |desired_size_px|.
ObserveImages(
MediaSessionImageType type, int32 minimum_size_px, int32 desired_size_px,
MediaControllerImageObserver observer);
}; };
// The observer for observing media controller events. This is different to a // The observer for observing media controller events. This is different to a
...@@ -76,3 +83,13 @@ interface MediaControllerObserver { ...@@ -76,3 +83,13 @@ interface MediaControllerObserver {
// the observer which actions can be used to control the session. // the observer which actions can be used to control the session.
MediaSessionActionsChanged(array<MediaSessionAction> action); MediaSessionActionsChanged(array<MediaSessionAction> action);
}; };
// The observer for observing when images associated with a media controller
// change. This is a separate observer because not all clients need to handle
// images.
interface MediaControllerImageObserver {
// Called when the observed media controller has a new image of |type|.
// It may be null if there is no image.
MediaControllerImageChanged(
MediaSessionImageType type, MediaImageBitmap? bitmap);
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment