Add mac-media-control

This commit is contained in:
wuggy 2026-04-05 16:53:53 +01:00
commit 503a1e48d1
9 changed files with 320 additions and 5 deletions

View file

@ -1795,12 +1795,16 @@ function HandleMediaControlAppCommand(action) {
}
function getWindowsUIUtils() {
if (AppConstants.platform != "win") {
return null;
}
try {
return Cc["@mozilla.org/windows-ui-utils;1"]
.getService(Ci.nsIWindowsUIUtils);
if (AppConstants.platform == "win") {
return Cc["@mozilla.org/windows-ui-utils;1"]
.getService(Ci.nsIWindowsUIUtils);
}
if (AppConstants.platform == "macosx") {
return Cc["@mozilla.org/mac-media-control;1"]
.getService(Ci.nsIMacMediaControl);
}
return null;
} catch (e) {
return null;
}

View file

@ -0,0 +1,23 @@
/* 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/. */
#ifndef mozilla_widget_MacMediaControlUtils_h__
#define mozilla_widget_MacMediaControlUtils_h__
#include "nsIMacMediaControl.h"
#define NS_MAC_MEDIACONTROL_CONTRACTID "@mozilla.org/mac-media-control;1"
class MacMediaControlUtils final : public nsIMacMediaControl {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMACMEDIACONTROL
MacMediaControlUtils() = default;
private:
~MacMediaControlUtils() = default;
};
#endif // mozilla_widget_MacMediaControlUtils_h__

View file

@ -0,0 +1,243 @@
/* 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/. */
#import <Cocoa/Cocoa.h>
#import <objc/message.h>
#include <dlfcn.h>
#include "MacMediaControlUtils.h"
#include "mozilla/Unused.h"
#include "nsCocoaUtils.h"
// This must be included last:
#include "nsObjCExceptions.h"
NS_IMPL_ISUPPORTS(MacMediaControlUtils, nsIMacMediaControl)
namespace {
static NSMutableDictionary* sNowPlayingInfo = nil;
static bool sTriedLoadMediaPlayerFramework = false;
static void* sMediaPlayerHandle = nullptr;
static void EnsureMediaPlayerFrameworkLoaded();
static NSString*
GetMediaPlayerStringConstant(const char* aSymbolName, NSString* aFallback)
{
EnsureMediaPlayerFrameworkLoaded();
if (!sMediaPlayerHandle) {
sMediaPlayerHandle = dlopen("/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer",
RTLD_LAZY | RTLD_NOLOAD);
if (!sMediaPlayerHandle) {
sMediaPlayerHandle = dlopen("/System/Library/Frameworks/MediaPlayer.framework/MediaPlayer",
RTLD_LAZY);
}
}
if (!sMediaPlayerHandle) {
return aFallback;
}
NSString* const* key = static_cast<NSString* const*>(dlsym(sMediaPlayerHandle, aSymbolName));
return (key && *key) ? *key : aFallback;
}
static NSString*
NowPlayingPlaybackRateKey()
{
static NSString* key = GetMediaPlayerStringConstant("MPNowPlayingInfoPropertyPlaybackRate",
@"playbackRate");
return key;
}
static NSString*
NowPlayingElapsedTimeKey()
{
static NSString* key = GetMediaPlayerStringConstant("MPNowPlayingInfoPropertyElapsedPlaybackTime",
@"elapsedPlaybackTime");
return key;
}
static NSString*
MediaItemTitleKey()
{
static NSString* key = GetMediaPlayerStringConstant("MPMediaItemPropertyTitle",
@"title");
return key;
}
static NSString*
MediaItemArtistKey()
{
static NSString* key = GetMediaPlayerStringConstant("MPMediaItemPropertyArtist",
@"artist");
return key;
}
static NSString*
MediaItemAlbumTitleKey()
{
static NSString* key = GetMediaPlayerStringConstant("MPMediaItemPropertyAlbumTitle",
@"albumTitle");
return key;
}
static void
EnsureMediaPlayerFrameworkLoaded()
{
if (sTriedLoadMediaPlayerFramework) {
return;
}
sTriedLoadMediaPlayerFramework = true;
NSString* frameworkPath = @"/System/Library/Frameworks/MediaPlayer.framework";
NSBundle* mediaPlayerBundle = [NSBundle bundleWithPath:frameworkPath];
if (mediaPlayerBundle) {
[mediaPlayerBundle load];
}
}
static id
GetNowPlayingInfoCenter()
{
EnsureMediaPlayerFrameworkLoaded();
Class centerClass = NSClassFromString(@"MPNowPlayingInfoCenter");
if (!centerClass) {
return nil;
}
SEL defaultCenterSel = NSSelectorFromString(@"defaultCenter");
if (![centerClass respondsToSelector:defaultCenterSel]) {
return nil;
}
return ((id (*)(id, SEL))objc_msgSend)(centerClass, defaultCenterSel);
}
static void
ApplyNowPlayingInfo()
{
id center = GetNowPlayingInfoCenter();
if (!center) {
return;
}
SEL setInfoSel = NSSelectorFromString(@"setNowPlayingInfo:");
if (![center respondsToSelector:setInfoSel]) {
return;
}
NSDictionary* info = (sNowPlayingInfo && [sNowPlayingInfo count] > 0)
? [NSDictionary dictionaryWithDictionary:sNowPlayingInfo]
: nil;
((void (*)(id, SEL, id))objc_msgSend)(center, setInfoSel, info);
}
static void
EnsureNowPlayingInfo()
{
if (!sNowPlayingInfo) {
sNowPlayingInfo = [[NSMutableDictionary alloc] init];
}
}
} // namespace
NS_IMETHODIMP
MacMediaControlUtils::SetSMTCPlaybackState(bool aPlaying)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
EnsureNowPlayingInfo();
[sNowPlayingInfo setObject:[NSNumber numberWithDouble:(aPlaying ? 1.0 : 0.0)]
forKey:NowPlayingPlaybackRateKey()];
[sNowPlayingInfo setObject:[NSNumber numberWithDouble:0.0]
forKey:NowPlayingElapsedTimeKey()];
id center = GetNowPlayingInfoCenter();
if (center) {
SEL setPlaybackStateSel = NSSelectorFromString(@"setPlaybackState:");
if ([center respondsToSelector:setPlaybackStateSel]) {
// MPNowPlayingPlaybackState: 1 = playing, 2 = paused
NSInteger state = aPlaying ? 1 : 2;
((void (*)(id, SEL, NSInteger))objc_msgSend)(center, setPlaybackStateSel, state);
}
}
ApplyNowPlayingInfo();
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
MacMediaControlUtils::SetSMTCMetadata(const nsAString& aTitle,
const nsAString& aArtist,
const nsAString& aAlbum)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
EnsureNowPlayingInfo();
NSString* title = nsCocoaUtils::ToNSString(aTitle);
NSString* artist = nsCocoaUtils::ToNSString(aArtist);
NSString* album = nsCocoaUtils::ToNSString(aAlbum);
NSString* fallbackTitle = [[NSProcessInfo processInfo] processName];
if (title && [title length] > 0) {
[sNowPlayingInfo setObject:title forKey:MediaItemTitleKey()];
} else if (fallbackTitle && [fallbackTitle length] > 0) {
[sNowPlayingInfo setObject:fallbackTitle forKey:MediaItemTitleKey()];
} else {
[sNowPlayingInfo removeObjectForKey:MediaItemTitleKey()];
}
if (artist && [artist length] > 0) {
[sNowPlayingInfo setObject:artist forKey:MediaItemArtistKey()];
} else {
[sNowPlayingInfo removeObjectForKey:MediaItemArtistKey()];
}
if (album && [album length] > 0) {
[sNowPlayingInfo setObject:album forKey:MediaItemAlbumTitleKey()];
} else {
[sNowPlayingInfo removeObjectForKey:MediaItemAlbumTitleKey()];
}
ApplyNowPlayingInfo();
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
MacMediaControlUtils::SetSMTCThumbnailURL(const nsAString& aThumbnailURL)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
// Best effort: Artwork publishing requires MediaPlayer artwork classes that
// vary by macOS version. Keep this no-op for compatibility.
Unused << aThumbnailURL;
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
MacMediaControlUtils::ClearSMTCMetadata()
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if (sNowPlayingInfo) {
[sNowPlayingInfo removeAllObjects];
}
ApplyNowPlayingInfo();
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}

View file

@ -21,6 +21,7 @@ EXPORTS += [
UNIFIED_SOURCES += [
'ComplexTextInputPanel.mm',
'GfxInfo.mm',
'MacMediaControlUtils.mm',
'NativeKeyBindings.mm',
'nsAppShell.mm',
'nsBidiKeyboard.mm',

View file

@ -8,6 +8,8 @@
#include "nsIServiceManager.h"
#include "nsPrintOptionsX.h"
#include "nsPrintSettingsX.h"
#include "nsCocoaUtils.h"
#include "nsIWebBrowserPrint.h"
// The constants for paper orientation were renamed in 10.9. __MAC_10_9 is
// defined on OS X 10.9 and later. Although 10.8 and earlier are not supported

View file

@ -72,6 +72,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeThemeCocoa)
#include "nsMacDockSupport.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport)
#include "MacMediaControlUtils.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(MacMediaControlUtils)
#include "nsMacWebAppUtils.h"
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacWebAppUtils)
@ -112,6 +115,7 @@ NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID);
NS_DEFINE_NAMED_CID(NS_MAC_MEDIA_CONTROL_CID);
NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID);
NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID);
NS_DEFINE_NAMED_CID(NS_MACSYSTEMSTATUSBAR_CID);
@ -148,6 +152,7 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
{ &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor },
{ &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor },
{ &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor },
{ &kNS_MAC_MEDIA_CONTROL_CID, false, NULL, MacMediaControlUtilsConstructor },
{ &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor },
{ &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor },
{ &kNS_MACSYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor },
@ -186,6 +191,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
{ "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID },
{ "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
{ "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID },
{ "@mozilla.org/mac-media-control;1", &kNS_MAC_MEDIA_CONTROL_CID },
{ "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID },
{ "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID },
{ "@mozilla.org/widget/macsystemstatusbar;1", &kNS_MACSYSTEMSTATUSBAR_CID },

View file

@ -31,6 +31,7 @@ if toolkit == 'windows':
elif toolkit == 'cocoa':
XPIDL_SOURCES += [
'nsIMacDockSupport.idl',
'nsIMacMediaControl.idl',
'nsIMacWebAppUtils.idl',
'nsIStandaloneNativeMenu.idl',
'nsISystemStatusBar.idl',

View file

@ -0,0 +1,31 @@
/* 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 "nsISupports.idl"
[scriptable, uuid(9d16f8ac-9d47-4f16-95cf-5ec9b42d3e11)]
interface nsIMacMediaControl : nsISupports
{
/**
* Publish playback state to macOS now playing surfaces.
*/
void setSMTCPlaybackState(in boolean aPlaying);
/**
* Publish media metadata to macOS now playing surfaces.
*/
void setSMTCMetadata(in AString aTitle,
in AString aArtist,
in AString aAlbum);
/**
* Publish media artwork URL. Best effort; may be ignored by implementation.
*/
void setSMTCThumbnailURL(in AString aThumbnailURL);
/**
* Clear published media metadata.
*/
void clearSMTCMetadata();
};

View file

@ -155,6 +155,10 @@
#define NS_MACWEBAPPUTILS_CID \
{ 0xe9096367, 0xddd9, 0x45e4, { 0xb7, 0x62, 0x49, 0xc0, 0xc1, 0x8b, 0x71, 0x19 } }
// {7e3bd1b8-e2f4-4fbe-9f1c-d6f7b98a215e}
#define NS_MAC_MEDIA_CONTROL_CID \
{ 0x7e3bd1b8, 0xe2f4, 0x4fbe, { 0x9f, 0x1c, 0xd6, 0xf7, 0xb9, 0x8a, 0x21, 0x5e } }
//-----------------------------------------------------------
//Printing
//-----------------------------------------------------------