Wrap Song in a broader Playback state object with stuff like volume and repeat mode

This commit is contained in:
Danielle McLean 2024-07-26 09:53:17 +10:00
parent 085bca7974
commit 68609f3d07
Signed by: 00dani
GPG key ID: 6854781A0488421C
24 changed files with 765 additions and 245 deletions

View file

@ -1,37 +1,11 @@
from collections.abc import Callable, Coroutine
from pathlib import Path
from typing import Literal
from AppKit import NSCompositingOperationCopy, NSImage, NSMakeRect
from corefoundationasyncio import CoreFoundationEventLoop
from Foundation import CGSize, NSMutableDictionary
from MediaPlayer import (
MPChangePlaybackPositionCommandEvent,
MPMediaItemArtwork,
MPMediaItemPropertyAlbumTitle,
MPMediaItemPropertyAlbumTrackNumber,
MPMediaItemPropertyArtist,
MPMediaItemPropertyArtwork,
MPMediaItemPropertyComposer,
MPMediaItemPropertyDiscNumber,
MPMediaItemPropertyGenre,
MPMediaItemPropertyPersistentID,
MPMediaItemPropertyPlaybackDuration,
MPMediaItemPropertyTitle,
MPMusicPlaybackState,
MPMusicPlaybackStatePaused,
MPMusicPlaybackStatePlaying,
MPMusicPlaybackStateStopped,
MPNowPlayingInfoCenter,
MPNowPlayingInfoMediaTypeAudio,
MPNowPlayingInfoMediaTypeNone,
MPNowPlayingInfoPropertyAssetURL,
MPNowPlayingInfoPropertyElapsedPlaybackTime,
MPNowPlayingInfoPropertyExternalContentIdentifier,
MPNowPlayingInfoPropertyMediaType,
MPNowPlayingInfoPropertyPlaybackQueueCount,
MPNowPlayingInfoPropertyPlaybackQueueIndex,
MPNowPlayingInfoPropertyPlaybackRate,
MPRemoteCommandCenter,
MPRemoteCommandEvent,
MPRemoteCommandHandlerStatus,
@ -39,100 +13,13 @@ from MediaPlayer import (
)
from ...config.model import CocoaReceiverConfig
from ...playback import Playback
from ...playback.state import PlaybackState
from ...player import Player
from ...song import PlaybackState, Song
from ...song_receiver import LoopFactory, Receiver
from ...tools.asyncio import run_background_task
from .persistent_id import song_to_persistent_id
def logo_to_ns_image() -> NSImage:
return NSImage.alloc().initByReferencingFile_(
str(Path(__file__).parent.parent.parent / "mpd/logo.svg")
)
def data_to_ns_image(data: bytes) -> NSImage:
return NSImage.alloc().initWithData_(data)
def ns_image_to_media_item_artwork(img: NSImage) -> MPMediaItemArtwork:
def resize(size: CGSize) -> NSImage:
new = NSImage.alloc().initWithSize_(size)
new.lockFocus()
img.drawInRect_fromRect_operation_fraction_(
NSMakeRect(0, 0, size.width, size.height),
NSMakeRect(0, 0, img.size().width, img.size().height),
NSCompositingOperationCopy,
1.0,
)
new.unlockFocus()
return new
return MPMediaItemArtwork.alloc().initWithBoundsSize_requestHandler_(
img.size(), resize
)
def playback_state_to_cocoa(state: PlaybackState) -> MPMusicPlaybackState:
mapping: dict[PlaybackState, MPMusicPlaybackState] = {
PlaybackState.play: MPMusicPlaybackStatePlaying,
PlaybackState.pause: MPMusicPlaybackStatePaused,
PlaybackState.stop: MPMusicPlaybackStateStopped,
}
return mapping[state]
def join_plural_field(field: list[str]) -> str | None:
if field:
return ", ".join(field)
return None
def song_to_media_item(song: Song) -> NSMutableDictionary:
nowplaying_info = nothing_to_media_item()
nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeAudio
nowplaying_info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = song.elapsed
nowplaying_info[MPNowPlayingInfoPropertyExternalContentIdentifier] = str(song.file)
nowplaying_info[MPNowPlayingInfoPropertyPlaybackQueueCount] = song.queue_length
nowplaying_info[MPNowPlayingInfoPropertyPlaybackQueueIndex] = song.queue_index
nowplaying_info[MPMediaItemPropertyPersistentID] = song_to_persistent_id(song)
nowplaying_info[MPMediaItemPropertyTitle] = song.title
nowplaying_info[MPMediaItemPropertyArtist] = join_plural_field(song.artist)
nowplaying_info[MPMediaItemPropertyAlbumTitle] = join_plural_field(song.album)
nowplaying_info[MPMediaItemPropertyAlbumTrackNumber] = song.track
nowplaying_info[MPMediaItemPropertyDiscNumber] = song.disc
nowplaying_info[MPMediaItemPropertyGenre] = join_plural_field(song.genre)
nowplaying_info[MPMediaItemPropertyComposer] = join_plural_field(song.composer)
nowplaying_info[MPMediaItemPropertyPlaybackDuration] = song.duration
if song.url is not None:
nowplaying_info[MPNowPlayingInfoPropertyAssetURL] = song.url.human_repr()
# MPD can't play back music at different rates, so we just want to set it
# to 1.0 if the song is playing. (Leave it at 0.0 if the song is paused.)
if song.state == PlaybackState.play:
nowplaying_info[MPNowPlayingInfoPropertyPlaybackRate] = 1.0
if song.art:
nowplaying_info[MPMediaItemPropertyArtwork] = ns_image_to_media_item_artwork(
data_to_ns_image(song.art.data)
)
return nowplaying_info
def nothing_to_media_item() -> NSMutableDictionary:
nowplaying_info = NSMutableDictionary.dictionary()
nowplaying_info[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaTypeNone
nowplaying_info[MPMediaItemPropertyArtwork] = MPD_LOGO
nowplaying_info[MPMediaItemPropertyTitle] = "MPD (stopped)"
nowplaying_info[MPNowPlayingInfoPropertyPlaybackRate] = 0.0
return nowplaying_info
MPD_LOGO = ns_image_to_media_item_artwork(logo_to_ns_image())
from .convert.playback_to_media_item import playback_to_media_item
from .convert.to_state import playback_state_to_cocoa
class CocoaLoopFactory(LoopFactory[CoreFoundationEventLoop]):
@ -191,13 +78,9 @@ class CocoaNowPlayingReceiver(Receiver):
# unpause with remote commands.
self.info_center.setPlaybackState_(MPMusicPlaybackStatePlaying)
async def update(self, song: Song | None) -> None:
if song:
self.info_center.setNowPlayingInfo_(song_to_media_item(song))
self.info_center.setPlaybackState_(playback_state_to_cocoa(song.state))
else:
self.info_center.setNowPlayingInfo_(nothing_to_media_item())
self.info_center.setPlaybackState_(MPMusicPlaybackStateStopped)
async def update(self, playback: Playback) -> None:
self.info_center.setNowPlayingInfo_(playback_to_media_item(playback))
self.info_center.setPlaybackState_(playback_state_to_cocoa(playback.song.state))
def _create_handler(
self, player: Callable[[], Coroutine[None, None, PlaybackState | None]]