kyleneideck / BackgroundMusic

Background Music, a macOS audio utility: automatically pause your music, set individual apps' volumes and record system audio.
GNU General Public License v2.0
15.52k stars 664 forks source link

Netease Cloud Music support #184

Open Sev73n opened 5 years ago

Sev73n commented 5 years ago

https://itunes.apple.com/cn/app/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90/id944848654?mt=12

Netease Cloud Music is the most popular music player in china,

please add it to your list

thank you !

7sDream commented 5 years ago

I try to read BGMMusicPlayer.h to add Netease Music Player support today, but, unfortunately, it does not support the AppleScript interface, like many other players, eg, Clementine(#26).

I wonder can I use other way to support it, like "System Event" of menu bar click or something. But I'm not familiar with Obj-c and Mac app development.

So, I will try, but donโ€™t hold too much hope. : (

Sev73n commented 5 years ago

I try to read BGMMusicPlayer.h to add Netease Music Player support today, but, unfortunately, it does not support the AppleScript interface, like many other players, eg, Clementine(#26).

I wonder can I use other way to support it, like "System Event" of menu bar click or something. But I'm not familiar with Obj-c and Mac app development.

So, I will try, but donโ€™t hold too much hope. : (

anyway, thank you!

7sDream commented 5 years ago

@Sev73n

I learn Objective-C today and finally made a usable version in the night after work, I recorded a video to show it.

If you want to try, you can build it from my fork:

git clone https://github.com/7sDream/BackgroundMusic.git
cd BackgroundMusic
git checkout add_netease_music_support
./build_and_install.sh

Notice: I use AppleScript to operating Netease Music App, which need high permissions, So you need Add Background Music App to the System Preferences -> Security & Privacy -> Privacy -> Accessibility group, like the following image shows:

image

Maybe you need restart Background Music.app after add it to the group.

Any feedback is welcome.


To @kyleneideck

The method I used is differ from the codebase, I use NSAppleScript class to exec Apple Script code in the implementation. Now I'm wondering, if I can make a PR like this?

You can see the code I adds from here. I'm a super newbie in Obj-C language and Mac App development, So any suggestion(code, var name, function name, format, etc) are grateful.

Thank your for creating this app, awesome ๐Ÿ˜„

7sDream commented 5 years ago

As I'm writing last comment, I found there is an issue when the frontmost window is in fullscreen mode or Netease Music is in fullscreen mode.

The issue is, in this situation, Netease Music will not respond to the menu click event until I click it's menu bar manually. ๐Ÿ˜ž

So I change to another way, use the Dock icon menu, and it works in fullscreen mode after my test.(But the menu UI will appear, a little annoying)

So, This is the new video for @Sev73n

And code diff for @kyleneideck .

But the code is still tricky, I would build and use it in my own Mac if this can't be merge, which is reasonable. So, It's up to you~

๐Ÿบ ๐Ÿ˜„

kyleneideck commented 5 years ago

Thanks for looking into this, @7sDream. I think it would be worth using your solution until we find a better way, so feel free to make a PR. We can show a warning dialog when Netease is selected, or something like that, to tell people it's experimental and that it might interrupt them occasionally.

I was trying to get a similar music player called Anghami working a while ago and ran into the same problems. The best solution I found was sending synthetic presses of the play/pause keyboard key using CGEventPost. It works, but macOS only lets you generate media key presses that go to every process. You can't use CGEventPostToPid to send the keypress to only one process. So if a different music player takes over the media keys, Background Music would start controlling it instead.

You can use CGEventPostToPid for normal keys, but the media keys are special NSSystemDefined keys: https://weblog.rogueamoeba.com/2007/09/29/apple-keyboard-media-key-event-handling/. It's pretty unfortunate because most music players support the media keys, so it would have worked for a lot of music players that don't have APIs. (Although, I think macOS has started requiring apps that generate mouse or keyboard events to be in System Preferences -> Security & Privacy -> Privacy -> Accessibility.)

I tried it with Netease and it seemed to work fine.

Maybe we could add a generic music player to Background Music that generates media key presses and just explain to the user how it works. Background Music has to be able to figure out whether the music player is running, playing and/or paused, though. That could be tricky.

I've also noticed that macOS handles the media keys a bit differently to the way it handled them when I looked into this before. (Either that or I just missed a big part of the process.) The process rcd handles keypresses for the play/pause button by using MediaRemote.framework (private) to send a TogglePlayPause command to mediaremoted via XPC.

Then mediaremoted plays/pauses your Now Playing app, which is the app that's shown under "Now Playing" in the macOS sidebar/notification area. This article has a good explanation: https://paper.tuisec.win/detail/e89f06a55e3dd47.

Some third-party music/media players use the private framework to become the Now Playing app, for example: https://github.com/plexinc/plex-media-player/blob/master/src/input/apple/InputAppleMediaKeys.mm#L134. (See also: https://github.com/WebKit/webkit/blob/bde8ac5bddc49527396f3adb576d9a101a8e4828/Source/WebCore/platform/audio/mac/MediaSessionManagerMac.mm#L202.)

It looks like Netease does the same, so I've been trying to figure out a way to tell mediaremoted to play/pause a specific music player. I'm still working on it, though.

7sDream commented 5 years ago

@kyleneideck Thanks for your advice and information about MediaKey.

I will try add a warning dialog and make an Pr this weekend if I'm not busy.

The media key solution seems great if we can just make the BGM Player receive the event, I will look into it.

kyleneideck commented 5 years ago

I think I've reverse engineered enough of the MediaRemote message to get play/pause working. Here's a proof-of-concept:

msg-mr.tar.gz

It should play/pause Netease, even when a different app is the Now Playing app and Netease isn't receiving media key presses.

I used XPoCe to log the XPC message sent by rcd when I press the play/pause key. (I hadn't realised there's an XPoCe version 2.) The message is mostly straightforward, but there are some parts I haven't figured out yet.

It's a dict with four keys: MRXPC_NOWPLAYING_PLAYER_PATH_DATA_KEY, MRXPC_COMMAND_KEY, MRXPC_MESSAGE_ID_KEY and MRXPC_COMMAND_OPTIONS_KEY. MRXPC_NOWPLAYING_PLAYER_PATH_DATA_KEY is encoded as a protobuf and MRXPC_COMMAND_OPTIONS_KEY is encoded as a binary plist. The others are ints.

@7sDream Could you try the proof-of-concept and see if it works for you? If it does, maybe you could use it in your PR. (Sorry the code is such a mess.)

I'm still working on figuring out how to tell whether Netease is playing, paused or stopped.