Wrap Song in a broader Playback state object with stuff like volume and repeat mode
This commit is contained in:
parent
085bca7974
commit
68609f3d07
24 changed files with 765 additions and 245 deletions
|
@ -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]]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue