Files
cantata/mac/macnowplaying.mm
David Hoyes 730bf0672a Integrate with the Mac Now Playing Info Center
This adds MacOS support for controlling cantata via hardware media keys,
or via the OS's Now Playing widget. Implements #614 and #1347.
2021-04-01 12:58:34 +01:00

173 lines
7.1 KiB
Plaintext

/*
* Cantata
*
* Copyright (c) 2021 David Hoyes <dphoyes@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "macnowplaying.h"
#include "mpd-interface/mpdconnection.h"
#include "gui/currentcover.h"
#include "gui/stdactions.h"
#include <QPointer>
#import <AppKit/AppKit.h>
#import <MediaPlayer/MediaPlayer.h>
struct MacNowPlaying::impl
{
QPointer<MPDStatus> status;
QString currentCover;
Song currentSong;
NSMutableDictionary *nowPlayingInfo;
impl()
: nowPlayingInfo([[NSMutableDictionary alloc] init])
{}
};
MacNowPlaying::MacNowPlaying(QObject *p)
: QObject(p)
, pimpl(new impl())
{
connect(this, SIGNAL(setSeekId(qint32, quint32)), MPDConnection::self(), SLOT(setSeekId(qint32, quint32)));
connect(CurrentCover::self(), SIGNAL(coverFile(const QString &)), this, SLOT(updateCurrentCover(const QString &)));
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull) {
if (!pimpl->status.isNull() && MPDState_Playing==pimpl->status->state()) {
StdActions::self()->playPauseTrackAction->trigger();
}
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull) {
if (!pimpl->status.isNull() && pimpl->status->playlistLength()>0 && MPDState_Playing!=pimpl->status->state()) {
StdActions::self()->playPauseTrackAction->trigger();
}
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull) {
StdActions::self()->stopPlaybackAction->trigger();
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull) {
StdActions::self()->playPauseTrackAction->trigger();
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull) {
StdActions::self()->nextTrackAction->trigger();
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull) {
StdActions::self()->prevTrackAction->trigger();
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
MPChangePlaybackPositionCommandEvent* seekEvent = static_cast<MPChangePlaybackPositionCommandEvent *>(event);
emit setSeekId(-1, seekEvent.positionTime);
return MPRemoteCommandHandlerStatusSuccess;
}];
commandCenter.changeRepeatModeCommand.enabled = NO;
commandCenter.changeShuffleModeCommand.enabled = NO;
commandCenter.changePlaybackRateCommand.enabled = NO;
commandCenter.seekBackwardCommand.enabled = NO;
commandCenter.seekForwardCommand.enabled = NO;
commandCenter.skipBackwardCommand.enabled = NO;
commandCenter.skipForwardCommand.enabled = NO;
commandCenter.ratingCommand.enabled = NO;
commandCenter.likeCommand.enabled = NO;
commandCenter.dislikeCommand.enabled = NO;
commandCenter.bookmarkCommand.enabled = NO;
commandCenter.enableLanguageOptionCommand.enabled = NO;
commandCenter.disableLanguageOptionCommand.enabled = NO;
}
MacNowPlaying::~MacNowPlaying()
{}
void MacNowPlaying::updateStatus(MPDStatus * const status)
{
// If the current song has not yet been updated, reject this status
// update and wait for the next unless the play queue has recently
// been emptied or the connection to MPD has been lost ...
if (status->songId()!=pimpl->currentSong.id && status->songId()!=-1) {
return;
}
if (status!=pimpl->status) {
pimpl->status = status;
}
MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
switch (status->state()) {
case MPDState_Playing: {
infoCenter.playbackState = MPNowPlayingPlaybackStatePlaying;
[pimpl->nowPlayingInfo setObject:@(1) forKey:MPNowPlayingInfoPropertyPlaybackRate];
break;
}
case MPDState_Paused: {
infoCenter.playbackState = MPNowPlayingPlaybackStatePaused;
[pimpl->nowPlayingInfo setObject:@(0) forKey:MPNowPlayingInfoPropertyPlaybackRate];
break;
}
default:
case MPDState_Stopped: {
infoCenter.playbackState = MPNowPlayingPlaybackStateStopped;
[pimpl->nowPlayingInfo setObject:@(0) forKey:MPNowPlayingInfoPropertyPlaybackRate];
break;
}
}
[pimpl->nowPlayingInfo setObject:@(status->guessedElapsed()) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
[pimpl->nowPlayingInfo setObject:@(status->timeTotal()) forKey:MPMediaItemPropertyPlaybackDuration];
infoCenter.nowPlayingInfo = pimpl->nowPlayingInfo;
}
void MacNowPlaying::updateCurrentCover(const QString &fileName)
{
if (fileName!=pimpl->currentCover) {
pimpl->currentCover=fileName;
NSImage *image = [[NSImage alloc] initByReferencingFile:pimpl->currentCover.toNSString()];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithBoundsSize:CGSizeMake(1000, 1000) requestHandler:^NSImage * _Nonnull(CGSize) {
return image;
}];
[pimpl->nowPlayingInfo setObject:artwork forKey:MPMediaItemPropertyArtwork];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = pimpl->nowPlayingInfo;
}
}
void MacNowPlaying::updateCurrentSong(const Song &song)
{
pimpl->currentSong = song;
[pimpl->nowPlayingInfo setObject:@(song.id) forKey:MPMediaItemPropertyPersistentID];
[pimpl->nowPlayingInfo setObject:@(MPMediaTypeAnyAudio) forKey:MPMediaItemPropertyMediaType];
[pimpl->nowPlayingInfo setObject:song.title.toNSString() forKey:MPMediaItemPropertyTitle];
[pimpl->nowPlayingInfo setObject:song.artist.toNSString() forKey:MPMediaItemPropertyArtist];
[pimpl->nowPlayingInfo setObject:song.album.toNSString() forKey:MPMediaItemPropertyAlbumTitle];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = pimpl->nowPlayingInfo;
}
#include "moc_macnowplaying.cpp"