| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/system_media_controls/win/system_media_controls_win.h" |
| |
| #include <systemmediatransportcontrolsinterop.h> |
| #include <windows.media.control.h> |
| #include <wrl/client.h> |
| #include <wrl/event.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_is_test.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/core_winrt_util.h" |
| #include "base/win/scoped_hstring.h" |
| #include "components/system_media_controls/system_media_controls_observer.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/win/singleton_hwnd.h" |
| |
| namespace system_media_controls { |
| |
| // For testing only. |
| base::RepeatingCallback<void(bool)>* |
| g_on_visibility_changed_for_testing_callback = nullptr; |
| |
| // static |
| std::unique_ptr<SystemMediaControls> SystemMediaControls::Create( |
| const std::string& product_name, |
| int window) { |
| auto service = std::make_unique<internal::SystemMediaControlsWin>(window); |
| if (service->Initialize()) |
| return std::move(service); |
| return nullptr; |
| } |
| |
| // static |
| void SystemMediaControls::SetVisibilityChangedCallbackForTesting( |
| base::RepeatingCallback<void(bool)>* callback) { |
| CHECK_IS_TEST(); |
| g_on_visibility_changed_for_testing_callback = callback; |
| } |
| |
| namespace internal { |
| |
| using ABI::Windows::Media::IPlaybackPositionChangeRequestedEventArgs; |
| using ABI::Windows::Media::ISystemMediaTransportControls; |
| using ABI::Windows::Media::ISystemMediaTransportControls2; |
| using ABI::Windows::Media::ISystemMediaTransportControlsButtonPressedEventArgs; |
| using ABI::Windows::Media::ISystemMediaTransportControlsTimelineProperties; |
| using ABI::Windows::Media::PlaybackPositionChangeRequestedEventArgs; |
| 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; |
| |
| SystemMediaControlsWin::SystemMediaControlsWin(int window) |
| : is_for_web_app_(window != -1), |
| web_app_window_(reinterpret_cast<HWND>(window)) {} |
| |
| SystemMediaControlsWin::~SystemMediaControlsWin() { |
| if (has_valid_button_pressed_registration_token_) { |
| DCHECK(system_media_controls_); |
| system_media_controls_->remove_ButtonPressed( |
| button_pressed_registration_token_); |
| if (has_valid_playback_position_change_requested_registration_token_) { |
| Microsoft::WRL::ComPtr<ISystemMediaTransportControls2> |
| system_media_controls_2; |
| HRESULT hr = system_media_controls_.As(&system_media_controls_2); |
| if (SUCCEEDED(hr)) { |
| system_media_controls_2->remove_PlaybackPositionChangeRequested( |
| playback_position_change_requested_registration_token_); |
| } |
| } |
| ClearMetadata(); |
| } |
| } |
| |
| bool SystemMediaControlsWin::Initialize() { |
| if (attempted_to_initialize_) |
| return initialized_; |
| |
| attempted_to_initialize_ = true; |
| |
| Microsoft::WRL::ComPtr<ISystemMediaTransportControlsInterop> interop; |
| HRESULT hr = base::win::GetActivationFactory< |
| ISystemMediaTransportControlsInterop, |
| RuntimeClass_Windows_Media_SystemMediaTransportControls>(&interop); |
| if (FAILED(hr)) |
| return false; |
| |
| if (is_for_web_app_) { |
| hr = interop->GetForWindow(web_app_window_, |
| IID_PPV_ARGS(&system_media_controls_)); |
| } else { |
| hr = interop->GetForWindow(gfx::SingletonHwnd::GetInstance()->hwnd(), |
| IID_PPV_ARGS(&system_media_controls_)); |
| } |
| if (FAILED(hr)) |
| return false; |
| |
| auto weak_ptr = weak_factory_.GetWeakPtr(); |
| auto button_pressed_handler = |
| Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler< |
| SystemMediaTransportControls*, |
| SystemMediaTransportControlsButtonPressedEventArgs*>>( |
| [weak_ptr]( |
| ISystemMediaTransportControls* sender, |
| ISystemMediaTransportControlsButtonPressedEventArgs* args) { |
| if (weak_ptr) { |
| weak_ptr.get()->ButtonPressed(sender, args); |
| } |
| return S_OK; |
| }); |
| hr = system_media_controls_->add_ButtonPressed( |
| button_pressed_handler.Get(), &button_pressed_registration_token_); |
| if (FAILED(hr)) |
| return false; |
| |
| has_valid_button_pressed_registration_token_ = true; |
| |
| hr = system_media_controls_->put_IsEnabled(true); |
| if (FAILED(hr)) |
| return false; |
| |
| OnEnabledStatusChangedForTesting(); |
| |
| 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; |
| } |
| |
| void SystemMediaControlsWin::AddObserver( |
| SystemMediaControlsObserver* observer) { |
| observers_.AddObserver(observer); |
| |
| if (initialized_) |
| observer->OnServiceReady(); |
| } |
| |
| void SystemMediaControlsWin::RemoveObserver( |
| SystemMediaControlsObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void SystemMediaControlsWin::SetEnabled(bool enabled) { |
| DCHECK(initialized_); |
| HRESULT hr = system_media_controls_->put_IsEnabled(enabled); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetIsNextEnabled(bool value) { |
| DCHECK(initialized_); |
| HRESULT hr = system_media_controls_->put_IsNextEnabled(value); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetIsPreviousEnabled(bool value) { |
| DCHECK(initialized_); |
| HRESULT hr = system_media_controls_->put_IsPreviousEnabled(value); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetIsPlayPauseEnabled(bool value) { |
| DCHECK(initialized_); |
| |
| HRESULT hr = system_media_controls_->put_IsPlayEnabled(value); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = system_media_controls_->put_IsPauseEnabled(value); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetIsStopEnabled(bool value) { |
| DCHECK(initialized_); |
| HRESULT hr = system_media_controls_->put_IsStopEnabled(value); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetIsSeekToEnabled(bool value) { |
| DCHECK(initialized_); |
| |
| Microsoft::WRL::ComPtr<ISystemMediaTransportControls2> |
| system_media_controls_2; |
| HRESULT hr = system_media_controls_.As(&system_media_controls_2); |
| if (FAILED(hr)) |
| return; |
| |
| if (value) { |
| auto weak_ptr = weak_factory_.GetWeakPtr(); |
| auto playback_position_change_requested_handler = |
| Microsoft::WRL::Callback<ABI::Windows::Foundation::ITypedEventHandler< |
| SystemMediaTransportControls*, |
| PlaybackPositionChangeRequestedEventArgs*>>( |
| [weak_ptr](ISystemMediaTransportControls* sender, |
| IPlaybackPositionChangeRequestedEventArgs* args) { |
| if (weak_ptr) { |
| weak_ptr.get()->PlaybackPositionChangeRequested(sender, args); |
| } |
| return S_OK; |
| }); |
| hr = system_media_controls_2->add_PlaybackPositionChangeRequested( |
| playback_position_change_requested_handler.Get(), |
| &playback_position_change_requested_registration_token_); |
| DCHECK(SUCCEEDED(hr)); |
| has_valid_playback_position_change_requested_registration_token_ = true; |
| } else { |
| if (has_valid_playback_position_change_requested_registration_token_) { |
| hr = system_media_controls_2->remove_PlaybackPositionChangeRequested( |
| playback_position_change_requested_registration_token_); |
| DCHECK(SUCCEEDED(hr)); |
| has_valid_playback_position_change_requested_registration_token_ = false; |
| } |
| } |
| } |
| |
| void SystemMediaControlsWin::SetPlaybackStatus(PlaybackStatus status) { |
| DCHECK(initialized_); |
| HRESULT hr = |
| system_media_controls_->put_PlaybackStatus(GetSmtcPlaybackStatus(status)); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetTitle(const std::u16string& title) { |
| DCHECK(initialized_); |
| DCHECK(display_properties_); |
| base::win::ScopedHString h_title = |
| base::win::ScopedHString::Create(base::UTF16ToWide(title)); |
| HRESULT hr = display_properties_->put_Title(h_title.get()); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetArtist(const std::u16string& artist) { |
| DCHECK(initialized_); |
| DCHECK(display_properties_); |
| base::win::ScopedHString h_artist = |
| base::win::ScopedHString::Create(base::UTF16ToWide(artist)); |
| HRESULT hr = display_properties_->put_Artist(h_artist.get()); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::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::optional<std::vector<uint8_t>> icon_png = |
| gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, /*discard_transparency=*/false); |
| if (icon_png) { |
| hr = icon_data_writer_->WriteBytes(icon_png.value().size(), |
| (BYTE*)icon_png.value().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 result = base::win::GetActivationFactory< |
| IRandomAccessStreamReferenceStatics, |
| RuntimeClass_Windows_Storage_Streams_RandomAccessStreamReference>( |
| &reference_statics); |
| DCHECK(SUCCEEDED(result)); |
| |
| result = reference_statics->CreateFromStream(icon_stream_.Get(), |
| &icon_stream_reference_); |
| DCHECK(SUCCEEDED(result)); |
| |
| result = |
| display_updater_->put_Thumbnail(icon_stream_reference_.Get()); |
| DCHECK(SUCCEEDED(result)); |
| |
| result = display_updater_->Update(); |
| DCHECK(SUCCEEDED(result)); |
| } |
| return hr; |
| }); |
| |
| hr = store_async_operation->put_Completed(store_async_callback.Get()); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::SetPosition( |
| const media_session::MediaPosition& position) { |
| DCHECK(initialized_); |
| |
| Microsoft::WRL::ComPtr<ISystemMediaTransportControls2> |
| system_media_controls_2; |
| HRESULT hr = system_media_controls_.As(&system_media_controls_2); |
| if (FAILED(hr)) |
| return; |
| |
| Microsoft::WRL::ComPtr<ISystemMediaTransportControlsTimelineProperties> |
| timeline_properties; |
| base::win::ScopedHString id = base::win::ScopedHString::Create( |
| RuntimeClass_Windows_Media_SystemMediaTransportControlsTimelineProperties); |
| hr = base::win::RoActivateInstance(id.get(), &timeline_properties); |
| DCHECK(SUCCEEDED(hr)); |
| |
| ABI::Windows::Foundation::TimeSpan timeSpanZero = {0}; |
| |
| hr = timeline_properties->put_MinSeekTime(timeSpanZero); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = timeline_properties->put_StartTime(timeSpanZero); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = timeline_properties->put_Position( |
| position.GetPosition().ToWinrtTimeSpan()); |
| DCHECK(SUCCEEDED(hr)); |
| |
| ABI::Windows::Foundation::TimeSpan duration = |
| position.duration().ToWinrtTimeSpan(); |
| |
| hr = timeline_properties->put_EndTime(duration); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = timeline_properties->put_MaxSeekTime(duration); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = system_media_controls_2->UpdateTimelineProperties( |
| timeline_properties.Get()); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = system_media_controls_2->put_PlaybackRate(position.playback_rate()); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::ClearThumbnail() { |
| DCHECK(initialized_); |
| DCHECK(display_updater_); |
| HRESULT hr = display_updater_->put_Thumbnail(nullptr); |
| DCHECK(SUCCEEDED(hr)); |
| |
| hr = display_updater_->Update(); |
| DCHECK(SUCCEEDED(hr)); |
| } |
| |
| void SystemMediaControlsWin::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)); |
| |
| OnEnabledStatusChangedForTesting(); |
| } |
| |
| void SystemMediaControlsWin::UpdateDisplay() { |
| DCHECK(initialized_); |
| DCHECK(system_media_controls_); |
| DCHECK(display_updater_); |
| HRESULT hr = system_media_controls_->put_IsEnabled(true); |
| DCHECK(SUCCEEDED(hr)); |
| |
| OnEnabledStatusChangedForTesting(); |
| |
| // |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)); |
| } |
| |
| bool SystemMediaControlsWin::GetVisibilityForTesting() const { |
| DCHECK(initialized_); |
| boolean is_enabled; |
| HRESULT hr = system_media_controls_->get_IsEnabled(&is_enabled); |
| DCHECK(SUCCEEDED(hr)); |
| return is_enabled; |
| } |
| |
| void SystemMediaControlsWin::OnPlay() { |
| for (SystemMediaControlsObserver& obs : observers_) |
| obs.OnPlay(this); |
| } |
| |
| void SystemMediaControlsWin::OnPause() { |
| for (SystemMediaControlsObserver& obs : observers_) |
| obs.OnPause(this); |
| } |
| |
| void SystemMediaControlsWin::OnNext() { |
| for (SystemMediaControlsObserver& obs : observers_) |
| obs.OnNext(this); |
| } |
| |
| void SystemMediaControlsWin::OnPrevious() { |
| for (SystemMediaControlsObserver& obs : observers_) |
| obs.OnPrevious(this); |
| } |
| |
| void SystemMediaControlsWin::OnStop() { |
| for (SystemMediaControlsObserver& obs : observers_) |
| obs.OnStop(this); |
| } |
| |
| void SystemMediaControlsWin::OnSeekTo(const base::TimeDelta& time) { |
| for (SystemMediaControlsObserver& obs : observers_) |
| obs.OnSeekTo(this, time); |
| } |
| |
| ABI::Windows::Media::MediaPlaybackStatus |
| SystemMediaControlsWin::GetSmtcPlaybackStatus(PlaybackStatus status) { |
| switch (status) { |
| case PlaybackStatus::kPlaying: |
| return ABI::Windows::Media::MediaPlaybackStatus:: |
| MediaPlaybackStatus_Playing; |
| case PlaybackStatus::kPaused: |
| return ABI::Windows::Media::MediaPlaybackStatus:: |
| MediaPlaybackStatus_Paused; |
| case PlaybackStatus::kStopped: |
| return ABI::Windows::Media::MediaPlaybackStatus:: |
| MediaPlaybackStatus_Stopped; |
| } |
| NOTREACHED(); |
| } |
| |
| HRESULT SystemMediaControlsWin::ButtonPressed( |
| ISystemMediaTransportControls* sender, |
| ISystemMediaTransportControlsButtonPressedEventArgs* args) { |
| SystemMediaTransportControlsButton button; |
| HRESULT hr = args->get_Button(&button); |
| if (FAILED(hr)) |
| return hr; |
| |
| switch (button) { |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Play: |
| OnPlay(); |
| break; |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Pause: |
| OnPause(); |
| break; |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Next: |
| OnNext(); |
| break; |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Previous: |
| OnPrevious(); |
| break; |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Stop: |
| OnStop(); |
| break; |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Record: |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_FastForward: |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_Rewind: |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_ChannelUp: |
| case SystemMediaTransportControlsButton:: |
| SystemMediaTransportControlsButton_ChannelDown: |
| break; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT SystemMediaControlsWin::PlaybackPositionChangeRequested( |
| ISystemMediaTransportControls* sender, |
| IPlaybackPositionChangeRequestedEventArgs* args) { |
| ABI::Windows::Foundation::TimeSpan position; |
| HRESULT hr = args->get_RequestedPlaybackPosition(&position); |
| if (FAILED(hr)) |
| return hr; |
| |
| OnSeekTo(base::TimeDelta::FromWinrtTimeSpan(position)); |
| |
| return S_OK; |
| } |
| |
| void SystemMediaControlsWin::OnEnabledStatusChangedForTesting() { |
| if (g_on_visibility_changed_for_testing_callback) { |
| g_on_visibility_changed_for_testing_callback->Run( |
| GetVisibilityForTesting()); |
| } |
| } |
| |
| } // namespace internal |
| |
| } // namespace system_media_controls |