Commit e250d01f authored by Austin Orion's avatar Austin Orion Committed by Commit Bot

Display metadata from the MediaSession API in the SMTC.

This change adds the ability to take metadata provided by websites
via the MediaSession API and give it to the System Media Transport
Controls (SMTC), giving users more information about the active media
session on Windows.

Bug: 954009
Change-Id: I637fce4208a5a78138cbdc1bcb93a954b698b7d0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1606900
Commit-Queue: Austin Orion <auorion@microsoft.com>
Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Reviewed-by: default avatarChrome Cunningham <chcunningham@chromium.org>
Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Reviewed-by: default avatarMounir Lamouri <mlamouri@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664099}
parent b0f2472d
......@@ -478,6 +478,7 @@
#include "chrome/browser/ui/passwords/google_password_manager_navigation_throttle.h"
#include "chrome/browser/ui/search/new_tab_page_navigation_throttle.h"
#include "chrome/common/importer/profile_import.mojom.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#endif // !defined(OS_ANDROID)
#if defined(OS_WIN) || defined(OS_MACOSX) || \
......@@ -5763,6 +5764,19 @@ blink::UserAgentMetadata ChromeContentBrowserClient::GetUserAgentMetadata()
return ::GetUserAgentMetadata();
}
base::Optional<gfx::ImageSkia> ChromeContentBrowserClient::GetProductLogo()
const {
// This icon is available on Android, but adds 19KiB to the APK. Since it
// isn't used on Android we exclude it to avoid bloat.
#if !defined(OS_ANDROID)
return base::Optional<gfx::ImageSkia>(
*ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_PRODUCT_LOGO_256));
#else
return base::nullopt;
#endif
}
bool ChromeContentBrowserClient::IsBuiltinComponent(
content::BrowserContext* browser_context,
const url::Origin& origin) {
......
......@@ -576,6 +576,8 @@ class ChromeContentBrowserClient : public content::ContentBrowserClient {
std::string GetUserAgent() const override;
blink::UserAgentMetadata GetUserAgentMetadata() const override;
base::Optional<gfx::ImageSkia> GetProductLogo() const override;
bool IsBuiltinComponent(content::BrowserContext* browser_context,
const url::Origin& origin) override;
......
......@@ -7,6 +7,7 @@
#include <memory>
#include <utility>
#include "content/public/browser/content_browser_client.h"
#include "services/media_session/public/mojom/constants.mojom.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
......@@ -16,6 +17,9 @@ namespace content {
using ABI::Windows::Media::MediaPlaybackStatus;
const int kMinImageSize = 71;
const int kDesiredImageSize = 150;
SystemMediaControlsNotifier::SystemMediaControlsNotifier(
service_manager::Connector* connector)
: connector_(connector) {}
......@@ -46,19 +50,28 @@ void SystemMediaControlsNotifier::Initialize() {
// Observe the active media controller for changes to playback state and
// supported actions.
media_session::mojom::MediaControllerObserverPtr media_controller_observer;
media_session::mojom::MediaControllerObserverPtr
media_controller_observer_ptr;
media_controller_observer_binding_.Bind(
mojo::MakeRequest(&media_controller_observer));
media_controller_ptr_->AddObserver(std::move(media_controller_observer));
mojo::MakeRequest(&media_controller_observer_ptr));
media_controller_ptr_->AddObserver(std::move(media_controller_observer_ptr));
// Observe the active media controller for changes to provided artwork.
media_session::mojom::MediaControllerImageObserverPtr image_observer_ptr;
media_controller_image_observer_binding_.Bind(
mojo::MakeRequest(&image_observer_ptr));
media_controller_ptr_->ObserveImages(
media_session::mojom::MediaSessionImageType::kArtwork, kMinImageSize,
kDesiredImageSize, std::move(image_observer_ptr));
}
void SystemMediaControlsNotifier::MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) {
media_session::mojom::MediaSessionInfoPtr session_info_ptr) {
DCHECK(service_);
session_info_ = std::move(session_info);
if (session_info_) {
if (session_info_->playback_state ==
session_info_ptr_ = std::move(session_info_ptr);
if (session_info_ptr_) {
if (session_info_ptr_->playback_state ==
media_session::mojom::MediaPlaybackState::kPlaying) {
service_->SetPlaybackStatus(
MediaPlaybackStatus::MediaPlaybackStatus_Playing);
......@@ -69,6 +82,58 @@ void SystemMediaControlsNotifier::MediaSessionInfoChanged(
} else {
service_->SetPlaybackStatus(
MediaPlaybackStatus::MediaPlaybackStatus_Stopped);
// These steps reference the Media Session Standard
// https://wicg.github.io/mediasession/#metadata
// 5.3.1 If the active media session is null, unset the media metadata
// presented to the platform, and terminate these steps.
service_->ClearMetadata();
}
}
void SystemMediaControlsNotifier::MediaSessionMetadataChanged(
const base::Optional<media_session::MediaMetadata>& metadata) {
DCHECK(service_);
if (metadata.has_value()) {
// 5.3.3 Update the media metadata presented to the platform to match the
// metadata for the active media session.
// If no title was provided, the title of the tab will be in the title
// property.
service_->SetTitle(metadata->title);
// If no artist was provided, then the source URL will be in the artist
// property.
service_->SetArtist(metadata->artist);
service_->UpdateDisplay();
} else {
// 5.3.2 If the metadata of the active media session is an empty metadata,
// unset the media metadata presented to the platform.
service_->ClearMetadata();
}
}
void SystemMediaControlsNotifier::MediaControllerImageChanged(
media_session::mojom::MediaSessionImageType type,
const SkBitmap& bitmap) {
DCHECK(service_);
if (!bitmap.empty()) {
// 5.3.4.4.3 If the image format is supported, use the image as the artwork
// for display in the platform UI. Otherwise the fetch image algorithm fails
// and terminates.
service_->SetThumbnail(bitmap);
} else {
// 5.3.4.2 If metadata's artwork is empty, terminate these steps.
// If no images are fetched in the fetch image algorithm, the user agent
// may have fallback behavior such as displaying a default image as artwork.
// We display the application icon if no artwork is provided.
base::Optional<gfx::ImageSkia> icon =
GetContentClient()->browser()->GetProductLogo();
if (icon.has_value())
service_->SetThumbnail(*icon->bitmap());
else
service_->ClearThumbnail();
}
}
......
......@@ -27,7 +27,8 @@ namespace content {
// metadata. It observes changes to the active Media Session and updates the
// SMTC accordingly.
class CONTENT_EXPORT SystemMediaControlsNotifier
: public media_session::mojom::MediaControllerObserver {
: public media_session::mojom::MediaControllerObserver,
public media_session::mojom::MediaControllerImageObserver {
public:
explicit SystemMediaControlsNotifier(service_manager::Connector* connector);
~SystemMediaControlsNotifier() override;
......@@ -38,13 +39,18 @@ class CONTENT_EXPORT SystemMediaControlsNotifier
void MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) override;
void MediaSessionMetadataChanged(
const base::Optional<media_session::MediaMetadata>& metadata) override {}
const base::Optional<media_session::MediaMetadata>& metadata) override;
void MediaSessionActionsChanged(
const std::vector<media_session::mojom::MediaSessionAction>& actions)
override {}
void MediaSessionChanged(
const base::Optional<base::UnguessableToken>& request_id) override {}
// media_session::mojom::MediaControllerImageObserver implementation.
void MediaControllerImageChanged(
::media_session::mojom::MediaSessionImageType type,
const SkBitmap& bitmap) override;
void SetSystemMediaControlsServiceForTesting(
system_media_controls::SystemMediaControlsService* service) {
service_ = service;
......@@ -59,11 +65,13 @@ class CONTENT_EXPORT SystemMediaControlsNotifier
// Tracks current media session state/metadata.
media_session::mojom::MediaControllerPtr media_controller_ptr_;
media_session::mojom::MediaSessionInfoPtr session_info_;
media_session::mojom::MediaSessionInfoPtr session_info_ptr_;
// Used to receive updates to the active media controller.
mojo::Binding<media_session::mojom::MediaControllerObserver>
media_controller_observer_binding_{this};
mojo::Binding<media_session::mojom::MediaControllerImageObserver>
media_controller_image_observer_binding_{this};
DISALLOW_COPY_AND_ASSIGN(SystemMediaControlsNotifier);
};
......
......@@ -18,6 +18,7 @@ using ABI::Windows::Media::MediaPlaybackStatus;
using media_session::mojom::MediaPlaybackState;
using media_session::mojom::MediaSessionInfo;
using media_session::mojom::MediaSessionInfoPtr;
using testing::_;
using testing::Expectation;
class SystemMediaControlsNotifierTest : public testing::Test {
......@@ -48,6 +49,29 @@ class SystemMediaControlsNotifierTest : public testing::Test {
void SimulateStopped() { notifier_->MediaSessionInfoChanged(nullptr); }
void SimulateMetadataChanged(bool empty,
base::string16 title,
base::string16 artist) {
if (!empty) {
media_session::MediaMetadata metadata;
metadata.title = title;
metadata.artist = artist;
notifier_->MediaSessionMetadataChanged(
base::Optional<media_session::MediaMetadata>(metadata));
} else {
notifier_->MediaSessionMetadataChanged(base::nullopt);
}
}
void SimulateImageChanged() {
// Need a non-empty SkBitmap so MediaControllerImageChanged doesn't try to
// get the icon from ChromeContentBrowserClient.
SkBitmap bitmap;
bitmap.allocN32Pixels(1, 1);
notifier_->MediaControllerImageChanged(
media_session::mojom::MediaSessionImageType::kArtwork, bitmap);
}
SystemMediaControlsNotifier& notifier() { return *notifier_; }
system_media_controls::testing::MockSystemMediaControlsService&
mock_system_media_controls_service() {
......@@ -71,14 +95,43 @@ TEST_F(SystemMediaControlsNotifierTest, ProperlyUpdatesPlaybackState) {
mock_system_media_controls_service(),
SetPlaybackStatus(MediaPlaybackStatus::MediaPlaybackStatus_Paused))
.After(playing);
EXPECT_CALL(
mock_system_media_controls_service(),
SetPlaybackStatus(MediaPlaybackStatus::MediaPlaybackStatus_Stopped))
.After(paused);
Expectation stopped =
EXPECT_CALL(
mock_system_media_controls_service(),
SetPlaybackStatus(MediaPlaybackStatus::MediaPlaybackStatus_Stopped))
.After(paused);
EXPECT_CALL(mock_system_media_controls_service(), ClearMetadata())
.After(stopped);
SimulatePlaying();
SimulatePaused();
SimulateStopped();
}
TEST_F(SystemMediaControlsNotifierTest, ProperlyUpdatesMetadata) {
base::string16 title = L"title";
base::string16 artist = L"artist";
EXPECT_CALL(mock_system_media_controls_service(), SetTitle(title));
EXPECT_CALL(mock_system_media_controls_service(), SetArtist(artist));
EXPECT_CALL(mock_system_media_controls_service(), ClearMetadata()).Times(0);
EXPECT_CALL(mock_system_media_controls_service(), UpdateDisplay());
SimulateMetadataChanged(false, title, artist);
}
TEST_F(SystemMediaControlsNotifierTest, ProperlyUpdatesNullMetadata) {
EXPECT_CALL(mock_system_media_controls_service(), SetTitle(_)).Times(0);
EXPECT_CALL(mock_system_media_controls_service(), SetArtist(_)).Times(0);
EXPECT_CALL(mock_system_media_controls_service(), ClearMetadata());
SimulateMetadataChanged(true, L"", L"");
}
TEST_F(SystemMediaControlsNotifierTest, ProperlyUpdatesImage) {
EXPECT_CALL(mock_system_media_controls_service(), SetThumbnail(_));
SimulateImageChanged();
}
} // namespace content
......@@ -963,6 +963,10 @@ blink::UserAgentMetadata ContentBrowserClient::GetUserAgentMetadata() const {
return blink::UserAgentMetadata();
}
base::Optional<gfx::ImageSkia> ContentBrowserClient::GetProductLogo() const {
return base::nullopt;
}
bool ContentBrowserClient::IsBuiltinComponent(BrowserContext* browser_context,
const url::Origin& origin) {
return false;
......
......@@ -1535,6 +1535,10 @@ class CONTENT_EXPORT ContentBrowserClient {
// Returns user agent metadata. Content may cache this value.
virtual blink::UserAgentMetadata GetUserAgentMetadata() const;
// Returns a 256x256 transparent background image of the product logo, i.e.
// the browser icon, if available.
virtual base::Optional<gfx::ImageSkia> GetProductLogo() const;
// Returns whether |origin| should be considered a integral component similar
// to native code, and as such whether its log messages should be recorded.
virtual bool IsBuiltinComponent(BrowserContext* browser_context,
......
......@@ -18,6 +18,10 @@ component("system_media_controls") {
"//base",
"//ui/gfx",
]
public_deps = [
"//skia",
]
}
static_library("test_support") {
......
......@@ -32,6 +32,12 @@ class MockSystemMediaControlsService : public SystemMediaControlsService {
MOCK_METHOD1(SetIsStopEnabled, void(bool value));
MOCK_METHOD1(SetPlaybackStatus,
void(ABI::Windows::Media::MediaPlaybackStatus status));
MOCK_METHOD1(SetTitle, void(const base::string16& title));
MOCK_METHOD1(SetArtist, void(const base::string16& artist));
MOCK_METHOD1(SetThumbnail, void(const SkBitmap& bitmap));
MOCK_METHOD0(ClearThumbnail, void());
MOCK_METHOD0(ClearMetadata, void());
MOCK_METHOD0(UpdateDisplay, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockSystemMediaControlsService);
......
......@@ -8,6 +8,8 @@
#include <windows.media.control.h>
#include "base/component_export.h"
#include "base/strings/string16.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace system_media_controls {
......@@ -40,6 +42,14 @@ class COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS) SystemMediaControlsService {
// Setters for metadata.
virtual void SetPlaybackStatus(
ABI::Windows::Media::MediaPlaybackStatus status) = 0;
virtual void SetTitle(const base::string16& title) = 0;
virtual void SetArtist(const base::string16& artist) = 0;
virtual void SetThumbnail(const SkBitmap& bitmap) = 0;
// Helpers for metadata.
virtual void ClearThumbnail() = 0;
virtual void ClearMetadata() = 0;
virtual void UpdateDisplay() = 0;
protected:
virtual ~SystemMediaControlsService();
......
......@@ -13,6 +13,7 @@
#include "base/win/core_winrt_util.h"
#include "base/win/scoped_hstring.h"
#include "ui/base/win/system_media_controls/system_media_controls_service_observer.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/win/singleton_hwnd.h"
namespace system_media_controls {
......@@ -24,6 +25,12 @@ using ABI::Windows::Media::ISystemMediaTransportControlsButtonPressedEventArgs;
using ABI::Windows::Media::SystemMediaTransportControls;
using ABI::Windows::Media::SystemMediaTransportControlsButton;
using ABI::Windows::Media::SystemMediaTransportControlsButtonPressedEventArgs;
using ABI::Windows::Storage::Streams::IDataWriter;
using ABI::Windows::Storage::Streams::IDataWriterFactory;
using ABI::Windows::Storage::Streams::IOutputStream;
using ABI::Windows::Storage::Streams::IRandomAccessStream;
using ABI::Windows::Storage::Streams::IRandomAccessStreamReference;
using ABI::Windows::Storage::Streams::IRandomAccessStreamReferenceStatics;
// static
SystemMediaControlsServiceImpl* SystemMediaControlsServiceImpl::GetInstance() {
......@@ -38,7 +45,7 @@ SystemMediaControlsServiceImpl::~SystemMediaControlsServiceImpl() {
if (has_valid_registration_token_) {
DCHECK(system_media_controls_);
system_media_controls_->remove_ButtonPressed(registration_token_);
system_media_controls_->put_IsEnabled(false);
ClearMetadata();
}
}
......@@ -81,6 +88,22 @@ bool SystemMediaControlsServiceImpl::Initialize() {
if (FAILED(hr))
return false;
hr = system_media_controls_->get_DisplayUpdater(&display_updater_);
if (FAILED(hr))
return false;
// The current MediaSession API implementation matches the SMTC music type
// most closely, since MediaSession has the artist property which the SMTC
// only presents to music playback types.
hr = display_updater_->put_Type(
ABI::Windows::Media::MediaPlaybackType::MediaPlaybackType_Music);
if (FAILED(hr))
return false;
hr = display_updater_->get_MusicProperties(&display_properties_);
if (FAILED(hr))
return false;
initialized_ = true;
return true;
}
......@@ -132,6 +155,138 @@ void SystemMediaControlsServiceImpl::SetPlaybackStatus(
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::SetTitle(const base::string16& title) {
DCHECK(initialized_);
DCHECK(display_properties_);
base::win::ScopedHString h_title = base::win::ScopedHString::Create(title);
HRESULT hr = display_properties_->put_Title(h_title.get());
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::SetArtist(const base::string16& artist) {
DCHECK(initialized_);
DCHECK(display_properties_);
base::win::ScopedHString h_artist = base::win::ScopedHString::Create(artist);
HRESULT hr = display_properties_->put_Artist(h_artist.get());
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::SetThumbnail(const SkBitmap& bitmap) {
DCHECK(initialized_);
DCHECK(display_updater_);
// Use |icon_data_writer_| to write the bitmap data into |icon_stream_| so we
// can populate |icon_stream_reference_| and then give it to the SMTC. All of
// these are member variables to avoid a race condition between them being
// destructed and the async operation completing.
base::win::ScopedHString id = base::win::ScopedHString::Create(
RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream);
HRESULT hr = base::win::RoActivateInstance(id.get(), &icon_stream_);
DCHECK(SUCCEEDED(hr));
Microsoft::WRL::ComPtr<IDataWriterFactory> data_writer_factory;
hr = base::win::GetActivationFactory<
IDataWriterFactory, RuntimeClass_Windows_Storage_Streams_DataWriter>(
&data_writer_factory);
DCHECK(SUCCEEDED(hr));
Microsoft::WRL::ComPtr<IOutputStream> output_stream;
hr = icon_stream_.As(&output_stream);
DCHECK(SUCCEEDED(hr));
hr = data_writer_factory->CreateDataWriter(output_stream.Get(),
&icon_data_writer_);
DCHECK(SUCCEEDED(hr));
std::vector<unsigned char> icon_png;
gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &icon_png);
hr = icon_data_writer_->WriteBytes(icon_png.size(), (BYTE*)icon_png.data());
DCHECK(SUCCEEDED(hr));
// Store the written bytes in the stream, an async operation.
Microsoft::WRL::ComPtr<
ABI::Windows::Foundation::IAsyncOperation<unsigned int>>
store_async_operation;
hr = icon_data_writer_->StoreAsync(&store_async_operation);
DCHECK(SUCCEEDED(hr));
// Make a callback that gives the icon to the SMTC once the bits make it into
// |icon_stream_|
auto store_async_callback = Microsoft::WRL::Callback<
ABI::Windows::Foundation::IAsyncOperationCompletedHandler<unsigned int>>(
[this](ABI::Windows::Foundation::IAsyncOperation<unsigned int>* async_op,
ABI::Windows::Foundation::AsyncStatus status) mutable {
// Check the async operation completed successfully.
ABI::Windows::Foundation::IAsyncInfo* async_info;
HRESULT hr = async_op->QueryInterface(
IID_IAsyncInfo, reinterpret_cast<void**>(&async_info));
DCHECK(SUCCEEDED(hr));
async_info->get_ErrorCode(&hr);
if (SUCCEEDED(hr) &&
status == ABI::Windows::Foundation::AsyncStatus::Completed) {
Microsoft::WRL::ComPtr<IRandomAccessStreamReferenceStatics>
reference_statics;
HRESULT hr = base::win::GetActivationFactory<
IRandomAccessStreamReferenceStatics,
RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference>(
&reference_statics);
DCHECK(SUCCEEDED(hr));
hr = reference_statics->CreateFromStream(icon_stream_.Get(),
&icon_stream_reference_);
DCHECK(SUCCEEDED(hr));
hr = display_updater_->put_Thumbnail(icon_stream_reference_.Get());
DCHECK(SUCCEEDED(hr));
hr = display_updater_->Update();
DCHECK(SUCCEEDED(hr));
}
return hr;
});
hr = store_async_operation->put_Completed(store_async_callback.Get());
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::ClearThumbnail() {
DCHECK(initialized_);
DCHECK(display_updater_);
HRESULT hr = display_updater_->put_Thumbnail(nullptr);
DCHECK(SUCCEEDED(hr));
hr = display_updater_->Update();
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::ClearMetadata() {
DCHECK(initialized_);
DCHECK(display_updater_);
HRESULT hr = display_updater_->ClearAll();
DCHECK(SUCCEEDED(hr));
// To prevent disabled controls and the executable name from showing up in the
// SMTC, we need to tell them that we are disabled.
hr = system_media_controls_->put_IsEnabled(false);
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::UpdateDisplay() {
DCHECK(initialized_);
DCHECK(system_media_controls_);
DCHECK(display_updater_);
HRESULT hr = system_media_controls_->put_IsEnabled(true);
DCHECK(SUCCEEDED(hr));
// |ClearAll()| unsets the type, if we don't set it again then the artist
// won't be displayed.
hr = display_updater_->put_Type(
ABI::Windows::Media::MediaPlaybackType::MediaPlaybackType_Music);
DCHECK(SUCCEEDED(hr));
hr = display_updater_->Update();
DCHECK(SUCCEEDED(hr));
}
void SystemMediaControlsServiceImpl::OnPlay() {
for (SystemMediaControlsServiceObserver& obs : observers_)
obs.OnPlay();
......
......@@ -44,6 +44,12 @@ class COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS) SystemMediaControlsServiceImpl
void SetIsStopEnabled(bool value) override;
void SetPlaybackStatus(
ABI::Windows::Media::MediaPlaybackStatus status) override;
void SetTitle(const base::string16& title) override;
void SetArtist(const base::string16& artist) override;
void SetThumbnail(const SkBitmap& bitmap) override;
void ClearThumbnail() override;
void ClearMetadata() override;
void UpdateDisplay() override;
private:
friend struct base::DefaultSingletonTraits<SystemMediaControlsServiceImpl>;
......@@ -60,8 +66,22 @@ class COMPONENT_EXPORT(SYSTEM_MEDIA_CONTROLS) SystemMediaControlsServiceImpl
void OnPrevious();
void OnStop();
// Control and keep track of the metadata.
Microsoft::WRL::ComPtr<ABI::Windows::Media::ISystemMediaTransportControls>
system_media_controls_;
Microsoft::WRL::ComPtr<
ABI::Windows::Media::ISystemMediaTransportControlsDisplayUpdater>
display_updater_;
Microsoft::WRL::ComPtr<ABI::Windows::Media::IMusicDisplayProperties>
display_properties_;
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IDataWriter>
icon_data_writer_;
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream>
icon_stream_;
Microsoft::WRL::ComPtr<
ABI::Windows::Storage::Streams::IRandomAccessStreamReference>
icon_stream_reference_;
EventRegistrationToken registration_token_;
// True if we've already tried to connect to the SystemMediaTransportControls.
......
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