| // 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/mac/system_media_controls_mac.h" |
| |
| #include "base/check_is_test.h" |
| #include "base/notimplemented.h" |
| #include "components/remote_cocoa/browser/application_host.h" |
| #include "components/system_media_controls/mac/remote_cocoa/system_media_controls_bridge.h" |
| #include "components/system_media_controls/system_media_controls_observer.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| |
| namespace { |
| system_media_controls::mojom::PlaybackStatus ConvertPlaybackStatus( |
| system_media_controls::SystemMediaControls::PlaybackStatus status) { |
| switch (status) { |
| case system_media_controls::SystemMediaControls::PlaybackStatus::kPlaying: |
| return system_media_controls::mojom::PlaybackStatus::kPlaying; |
| case system_media_controls::SystemMediaControls::PlaybackStatus::kPaused: |
| return system_media_controls::mojom::PlaybackStatus::kPaused; |
| case system_media_controls::SystemMediaControls::PlaybackStatus::kStopped: |
| return system_media_controls::mojom::PlaybackStatus::kStopped; |
| } |
| } |
| } // namespace |
| |
| 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( |
| remote_cocoa::ApplicationHost* application_host) { |
| return std::make_unique<internal::SystemMediaControlsMac>(application_host); |
| } |
| // static |
| void SystemMediaControls::SetVisibilityChangedCallbackForTesting( |
| base::RepeatingCallback<void(bool)>* callback) { |
| CHECK_IS_TEST(); |
| g_on_visibility_changed_for_testing_callback = callback; |
| } |
| |
| namespace internal { |
| |
| SystemMediaControlsMac::SystemMediaControlsMac( |
| remote_cocoa::ApplicationHost* application_host) |
| : application_host_(application_host) { |
| if (application_host) { |
| // ApplicationHost only has a value for PWAs. Use it to make an |
| // out-of-process SystemMediaControlsBridge in the correct app shim process. |
| remote_cocoa::mojom::Application* application_bridge = |
| application_host->GetApplication(); |
| CHECK(application_bridge); |
| |
| application_bridge->CreateSystemMediaControlsBridge( |
| bridge_remote_.BindNewPipeAndPassReceiver(), |
| bridge_receiver_.BindNewPipeAndPassRemote()); |
| |
| DCHECK(bridge_remote_.is_bound()); |
| DCHECK(bridge_receiver_.is_bound()); |
| DCHECK(bridge_remote_.is_connected()); |
| |
| // Additionally, if we're making an out of process bridge, we should |
| // observe `application_host` so we can get notified when the app shim |
| // is going away. |
| application_host->AddObserver(this); |
| } else { |
| // This SMC is for the browser, make an in-process bridge. |
| in_proc_bridge_ = std::make_unique<SystemMediaControlsBridge>( |
| bridge_remote_.BindNewPipeAndPassReceiver(), |
| bridge_receiver_.BindNewPipeAndPassRemote()); |
| } |
| } |
| |
| SystemMediaControlsMac::~SystemMediaControlsMac() { |
| if (application_host_) { |
| application_host_->RemoveObserver(this); |
| application_host_ = nullptr; |
| } |
| } |
| |
| void SystemMediaControlsMac::AddObserver( |
| system_media_controls::SystemMediaControlsObserver* observer) { |
| MaybeRebindToBridge(); |
| observers_.AddObserver(observer); |
| } |
| |
| void SystemMediaControlsMac::RemoveObserver( |
| system_media_controls::SystemMediaControlsObserver* observer) { |
| MaybeRebindToBridge(); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void SystemMediaControlsMac::SetIsNextEnabled(bool value) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetIsNextEnabled(value); |
| } |
| |
| void SystemMediaControlsMac::SetIsPreviousEnabled(bool value) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetIsPreviousEnabled(value); |
| } |
| |
| void SystemMediaControlsMac::SetIsPlayPauseEnabled(bool value) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetIsPlayPauseEnabled(value); |
| } |
| |
| void SystemMediaControlsMac::SetIsStopEnabled(bool value) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetIsStopEnabled(value); |
| } |
| |
| void SystemMediaControlsMac::SetIsSeekToEnabled(bool value) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetIsSeekToEnabled(value); |
| } |
| |
| void SystemMediaControlsMac::SetPlaybackStatus(PlaybackStatus status) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetPlaybackStatus(ConvertPlaybackStatus(status)); |
| } |
| |
| void SystemMediaControlsMac::SetTitle(const std::u16string& title) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetTitle(title); |
| } |
| |
| void SystemMediaControlsMac::SetArtist(const std::u16string& artist) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetArtist(artist); |
| } |
| |
| void SystemMediaControlsMac::SetAlbum(const std::u16string& album) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetAlbum(album); |
| } |
| |
| void SystemMediaControlsMac::SetThumbnail(const SkBitmap& bitmap) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetThumbnail(bitmap); |
| } |
| |
| void SystemMediaControlsMac::SetPosition( |
| const media_session::MediaPosition& position) { |
| MaybeRebindToBridge(); |
| bridge_remote_->SetPosition(position); |
| } |
| |
| void SystemMediaControlsMac::ClearMetadata() { |
| MaybeRebindToBridge(); |
| bridge_remote_->ClearMetadata(); |
| } |
| |
| bool SystemMediaControlsMac::GetVisibilityForTesting() const { |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| |
| void SystemMediaControlsMac::SetOnBridgeCreatedCallbackForTesting( |
| base::RepeatingCallback<void()> callback) { |
| on_bridge_created_callback_for_testing_ = callback; |
| } |
| |
| // These need to go tell the browser that Mac did something. |
| // These used to live in RemoteCommandCenterDelegate, but now move here since |
| // the observers list moved here. |
| void SystemMediaControlsMac::OnNext() { |
| for (auto& observer : observers_) { |
| observer.OnNext(this); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnPrevious() { |
| for (auto& observer : observers_) { |
| observer.OnPrevious(this); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnPause() { |
| for (auto& observer : observers_) { |
| observer.OnPause(this); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnPlayPause() { |
| for (auto& observer : observers_) { |
| observer.OnPlayPause(this); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnStop() { |
| for (auto& observer : observers_) { |
| observer.OnStop(this); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnPlay() { |
| for (auto& observer : observers_) { |
| observer.OnPlay(this); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnSeekTo(base::TimeDelta seek_time) { |
| for (auto& observer : observers_) { |
| observer.OnSeekTo(this, seek_time); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnBridgeCreatedForTesting() { |
| // The app shim has just told us that the SMCBridge was created. Update any |
| // tests that are listening. |
| if (on_bridge_created_callback_for_testing_) { |
| std::move(on_bridge_created_callback_for_testing_).Run(); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnMetadataClearedForTesting() { |
| if (g_on_visibility_changed_for_testing_callback) { |
| // The mojo test API told us that the metadata has been cleared. |
| // We are using this as a best-approximate signal that the visibility of the |
| // controls has changed, so run the callback with false, as clearing |
| // metadata implies that the controls should be hidden soon. |
| CHECK_IS_TEST(); |
| g_on_visibility_changed_for_testing_callback->Run(false); |
| } |
| } |
| |
| void SystemMediaControlsMac::OnApplicationHostDestroying( |
| remote_cocoa::ApplicationHost* host) { |
| // If we get here, the user has likely Cmd+Q quit the app. |
| application_host_->RemoveObserver(this); |
| application_host_ = nullptr; |
| } |
| |
| void SystemMediaControlsMac::MaybeRebindToBridge() { |
| if (bridge_remote_ && bridge_remote_.is_connected()) { |
| return; |
| } |
| |
| // Don't try to rebind if we've received OnApplicationHostDestroying, as |
| // ApplicationBridge will have gone away. |
| if (application_host_) { |
| // Before we reset our connections, clear the existing metadata to ensure |
| // we don't mix data between the mojo connections. |
| bridge_remote_->ClearMetadata(); |
| |
| bridge_remote_.reset(); |
| bridge_receiver_.reset(); |
| |
| remote_cocoa::mojom::Application* application_bridge = |
| application_host_->GetApplication(); |
| DCHECK(application_bridge); |
| |
| // We need to reconnect. |
| application_bridge->CreateSystemMediaControlsBridge( |
| bridge_remote_.BindNewPipeAndPassReceiver(), |
| bridge_receiver_.BindNewPipeAndPassRemote()); |
| } |
| } |
| |
| } // namespace internal |
| } // namespace system_media_controls |