Source code

Revision control

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MediaControlKeyManager.h"
#include "MediaControlUtils.h"
#include "MediaControlService.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/widget/MediaKeysEventSourceFactory.h"
#include "nsContentUtils.h"
#include "nsIObserverService.h"
#undef LOG
#define LOG(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
("MediaControlKeyManager=%p, " msg, this, ##__VA_ARGS__))
#undef LOG_INFO
#define LOG_INFO(msg, ...) \
MOZ_LOG(gMediaControlLog, LogLevel::Info, \
("MediaControlKeyManager=%p, " msg, this, ##__VA_ARGS__))
#define MEDIA_CONTROL_PREF "media.hardwaremediakeys.enabled"
namespace mozilla::dom {
bool MediaControlKeyManager::IsOpened() const {
return mEventSource && mEventSource->IsOpened();
}
bool MediaControlKeyManager::Open() {
if (IsOpened()) {
return true;
}
const bool isEnabledMediaControl = StartMonitoringControlKeys();
if (isEnabledMediaControl) {
RefPtr<MediaControlService> service = MediaControlService::GetService();
MOZ_ASSERT(service);
service->NotifyMediaControlHasEverBeenEnabled();
}
return isEnabledMediaControl;
}
void MediaControlKeyManager::Close() {
// We don't call parent's `Close()` because we want to keep the listener
// (MediaControlKeyHandler) all the time. It would be manually removed by
// `MediaControlService` when shutdown.
StopMonitoringControlKeys();
}
MediaControlKeyManager::MediaControlKeyManager()
: mObserver(new Observer(this)) {
nsContentUtils::RegisterShutdownObserver(mObserver);
Preferences::AddStrongObserver(mObserver, MEDIA_CONTROL_PREF);
}
MediaControlKeyManager::~MediaControlKeyManager() { Shutdown(); }
void MediaControlKeyManager::Shutdown() {
StopMonitoringControlKeys();
mEventSource = nullptr;
if (mObserver) {
nsContentUtils::UnregisterShutdownObserver(mObserver);
Preferences::RemoveObserver(mObserver, MEDIA_CONTROL_PREF);
mObserver = nullptr;
}
}
bool MediaControlKeyManager::StartMonitoringControlKeys() {
if (!StaticPrefs::media_hardwaremediakeys_enabled()) {
return false;
}
if (!mEventSource) {
mEventSource = widget::CreateMediaControlKeySource();
}
if (mEventSource && mEventSource->Open()) {
LOG_INFO("StartMonitoringControlKeys");
mEventSource->SetPlaybackState(mPlaybackState);
mEventSource->SetMediaMetadata(mMetadata);
mEventSource->SetSupportedMediaKeys(mSupportedKeys);
mEventSource->AddListener(this);
return true;
}
// Fail to open or create event source (eg. when cross-compiling with MinGW,
// we cannot use the related WinAPI)
return false;
}
void MediaControlKeyManager::StopMonitoringControlKeys() {
if (!mEventSource || !mEventSource->IsOpened()) {
return;
}
LOG_INFO("StopMonitoringControlKeys");
mEventSource->Close();
if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
// Close the source would reset the displayed playback state and metadata.
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
obs->NotifyObservers(nullptr, "media-displayed-playback-changed",
nullptr);
obs->NotifyObservers(nullptr, "media-displayed-metadata-changed",
nullptr);
}
}
}
void MediaControlKeyManager::OnActionPerformed(
const MediaControlAction& aAction) {
for (auto listener : mListeners) {
listener->OnActionPerformed(aAction);
}
}
void MediaControlKeyManager::SetPlaybackState(
MediaSessionPlaybackState aState) {
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetPlaybackState(aState);
}
mPlaybackState = aState;
LOG_INFO("playbackState=%s", ToMediaSessionPlaybackStateStr(mPlaybackState));
if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
obs->NotifyObservers(nullptr, "media-displayed-playback-changed",
nullptr);
}
}
}
MediaSessionPlaybackState MediaControlKeyManager::GetPlaybackState() const {
return (mEventSource && mEventSource->IsOpened())
? mEventSource->GetPlaybackState()
: mPlaybackState;
}
void MediaControlKeyManager::SetMediaMetadata(
const MediaMetadataBase& aMetadata) {
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetMediaMetadata(aMetadata);
}
mMetadata = aMetadata;
LOG_INFO("title=%s, artist=%s album=%s",
NS_ConvertUTF16toUTF8(mMetadata.mTitle).get(),
NS_ConvertUTF16toUTF8(mMetadata.mArtist).get(),
NS_ConvertUTF16toUTF8(mMetadata.mAlbum).get());
if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
obs->NotifyObservers(nullptr, "media-displayed-metadata-changed",
nullptr);
}
}
}
void MediaControlKeyManager::SetSupportedMediaKeys(
const MediaKeysArray& aSupportedKeys) {
mSupportedKeys.Clear();
for (const auto& key : aSupportedKeys) {
LOG_INFO("Supported keys=%s", ToMediaControlKeyStr(key));
mSupportedKeys.AppendElement(key);
}
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetSupportedMediaKeys(mSupportedKeys);
}
}
void MediaControlKeyManager::SetEnableFullScreen(bool aIsEnabled) {
LOG_INFO("Set fullscreen %s", aIsEnabled ? "enabled" : "disabled");
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetEnableFullScreen(aIsEnabled);
}
}
void MediaControlKeyManager::SetEnablePictureInPictureMode(bool aIsEnabled) {
LOG_INFO("Set Picture-In-Picture mode %s",
aIsEnabled ? "enabled" : "disabled");
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetEnablePictureInPictureMode(aIsEnabled);
}
}
void MediaControlKeyManager::SetPositionState(const PositionState& aState) {
LOG_INFO("Set PositionState, duration=%f, playbackRate=%f, position=%f",
aState.mDuration, aState.mPlaybackRate,
aState.mLastReportedPlaybackPosition);
if (mEventSource && mEventSource->IsOpened()) {
mEventSource->SetPositionState(aState);
}
}
void MediaControlKeyManager::OnPreferenceChange() {
const bool isPrefEnabled = StaticPrefs::media_hardwaremediakeys_enabled();
// Only start monitoring control keys when the pref is on and having a main
// controller that means already having media which need to be controlled.
const bool shouldMonitorKeys =
isPrefEnabled && MediaControlService::GetService()->GetMainController();
LOG_INFO("Preference change : %s media control",
isPrefEnabled ? "enable" : "disable");
if (shouldMonitorKeys) {
Unused << StartMonitoringControlKeys();
} else {
StopMonitoringControlKeys();
}
}
NS_IMPL_ISUPPORTS(MediaControlKeyManager::Observer, nsIObserver);
MediaControlKeyManager::Observer::Observer(MediaControlKeyManager* aManager)
: mManager(aManager) {}
NS_IMETHODIMP
MediaControlKeyManager::Observer::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
mManager->Shutdown();
} else if (!strcmp(aTopic, "nsPref:changed")) {
mManager->OnPreferenceChange();
}
return NS_OK;
}
} // namespace mozilla::dom