Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
28748df3c1 | |||
c2f67c4781 | |||
413df0979d | |||
7dfd3f85e4 | |||
b41339a8c5 | |||
b9039b2ad4 |
9 changed files with 53 additions and 67 deletions
|
@ -5,9 +5,6 @@
|
|||
"kind": {
|
||||
"const": "cocoa",
|
||||
"default": "cocoa",
|
||||
"enum": [
|
||||
"cocoa"
|
||||
],
|
||||
"title": "Kind",
|
||||
"type": "string"
|
||||
}
|
||||
|
@ -52,28 +49,14 @@
|
|||
"WebsocketsReceiverConfig": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "hostname",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"format": "hostname",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
],
|
||||
"description": "The hostname you'd like your WebSockets server to listen on. In most cases the default behaviour, which binds to all network interfaces, will be fine.",
|
||||
"title": "Host"
|
||||
"format": "hostname",
|
||||
"title": "Host",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"const": "websockets",
|
||||
"default": "websockets",
|
||||
"enum": [
|
||||
"websockets"
|
||||
],
|
||||
"title": "Kind",
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
"Queue": {
|
||||
"properties": {
|
||||
"current": {
|
||||
"description": "The zero-based index of the current song in MPD's queue.",
|
||||
"description": "The zero-based index of the current song in MPD's queue. If MPD is currently stopped, then there is no current song in the queue, indicated by None.",
|
||||
"title": "Current",
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -136,9 +136,6 @@
|
|||
},
|
||||
{
|
||||
"const": "oneshot",
|
||||
"enum": [
|
||||
"oneshot"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
|
@ -172,9 +169,6 @@
|
|||
},
|
||||
{
|
||||
"const": "oneshot",
|
||||
"enum": [
|
||||
"oneshot"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
|
@ -328,9 +322,6 @@
|
|||
"state": {
|
||||
"const": "stop",
|
||||
"default": "stop",
|
||||
"enum": [
|
||||
"stop"
|
||||
],
|
||||
"title": "State",
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class WebsocketsReceiverConfig(BaseReceiverConfig):
|
|||
#: The hostname you'd like your WebSockets server to listen on. In most
|
||||
#: cases the default behaviour, which binds to all network interfaces, will
|
||||
#: be fine.
|
||||
host: Optional[Host | tuple[Host, ...]] = None
|
||||
host: Optional[Host] = None
|
||||
|
||||
|
||||
ReceiverConfig = Annotated[
|
||||
|
|
|
@ -9,8 +9,8 @@ from .to_song import to_song
|
|||
|
||||
def to_queue(mpd: MpdState) -> Queue:
|
||||
return Queue(
|
||||
current=int(mpd.current["pos"]),
|
||||
next=int(mpd.status["nextsong"]),
|
||||
current=option_fmap(int, mpd.current.get("pos")),
|
||||
next=int(mpd.status.get("nextsong", 0)),
|
||||
length=int(mpd.status["playlistlength"]),
|
||||
)
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ from dataclasses import dataclass
|
|||
|
||||
@dataclass(slots=True)
|
||||
class Queue:
|
||||
#: The zero-based index of the current song in MPD's queue.
|
||||
current: int
|
||||
#: The zero-based index of the current song in MPD's queue. If MPD is
|
||||
#: currently stopped, then there is no current song in the queue, indicated
|
||||
#: by None.
|
||||
current: int | None
|
||||
#: The index of the next song to be played, taking into account random and
|
||||
#: repeat playback settings.
|
||||
next: int
|
||||
|
|
|
@ -37,4 +37,4 @@ def ns_image_to_media_item_artwork(img: NSImage) -> MPMediaItemArtwork:
|
|||
)
|
||||
|
||||
|
||||
MPD_LOGO = logo_to_ns_image()
|
||||
MPD_LOGO = ns_image_to_media_item_artwork(logo_to_ns_image())
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from collections.abc import Callable, Coroutine
|
||||
from typing import Literal
|
||||
|
||||
from corefoundationasyncio import CoreFoundationEventLoop
|
||||
from AppKit import NSApplication, NSApplicationActivationPolicyAccessory
|
||||
from MediaPlayer import (
|
||||
MPChangePlaybackPositionCommandEvent,
|
||||
MPMusicPlaybackStatePlaying,
|
||||
|
@ -12,6 +12,8 @@ from MediaPlayer import (
|
|||
MPRemoteCommandHandlerStatusSuccess,
|
||||
)
|
||||
|
||||
from corefoundationasyncio import CoreFoundationEventLoop
|
||||
|
||||
from ...config.model import CocoaReceiverConfig
|
||||
from ...playback import Playback
|
||||
from ...playback.state import PlaybackState
|
||||
|
@ -41,6 +43,9 @@ class CocoaNowPlayingReceiver(Receiver):
|
|||
pass
|
||||
|
||||
async def start(self, player: Player) -> None:
|
||||
NSApplication.sharedApplication().setActivationPolicy_(
|
||||
NSApplicationActivationPolicyAccessory
|
||||
)
|
||||
self.cmd_center = MPRemoteCommandCenter.sharedCommandCenter()
|
||||
self.info_center = MPNowPlayingInfoCenter.defaultCenter()
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
|||
|
||||
import ormsgpack
|
||||
from websockets import broadcast
|
||||
from websockets.server import WebSocketServerProtocol, serve
|
||||
from websockets.asyncio.server import Server, ServerConnection, serve
|
||||
from yarl import URL
|
||||
|
||||
from ...config.model import WebsocketsReceiverConfig
|
||||
|
@ -24,12 +24,11 @@ def default(value: object) -> object:
|
|||
class WebsocketsReceiver(Receiver):
|
||||
config: WebsocketsReceiverConfig
|
||||
player: Player
|
||||
connections: set[WebSocketServerProtocol]
|
||||
server: Server
|
||||
last_status: bytes = MSGPACK_NULL
|
||||
|
||||
def __init__(self, config: WebsocketsReceiverConfig):
|
||||
self.config = config
|
||||
self.connections = set()
|
||||
|
||||
@classmethod
|
||||
def loop_factory(cls) -> DefaultLoopFactory:
|
||||
|
@ -37,18 +36,14 @@ class WebsocketsReceiver(Receiver):
|
|||
|
||||
async def start(self, player: Player) -> None:
|
||||
self.player = player
|
||||
await serve(
|
||||
self.server = await serve(
|
||||
self.handle, host=self.config.host, port=self.config.port, reuse_port=True
|
||||
)
|
||||
|
||||
async def handle(self, conn: WebSocketServerProtocol) -> None:
|
||||
self.connections.add(conn)
|
||||
async def handle(self, conn: ServerConnection) -> None:
|
||||
await conn.send(self.last_status)
|
||||
try:
|
||||
await conn.wait_closed()
|
||||
finally:
|
||||
self.connections.remove(conn)
|
||||
|
||||
async def update(self, playback: Playback) -> None:
|
||||
self.last_status = ormsgpack.packb(playback, default=default)
|
||||
broadcast(self.connections, self.last_status)
|
||||
broadcast(self.server.connections, self.last_status)
|
||||
|
|
|
@ -2,6 +2,16 @@ from typing import Final, Literal
|
|||
|
||||
from Foundation import CGSize
|
||||
|
||||
NSApplicationActivationPolicyRegular: Final = 0
|
||||
NSApplicationActivationPolicyAccessory: Final = 1
|
||||
NSApplicationActivationPolicyProhibited: Final = 2
|
||||
NSApplicationActivationPolicy = Literal[0, 1, 2]
|
||||
|
||||
class NSApplication:
|
||||
@staticmethod
|
||||
def sharedApplication() -> NSApplication: ...
|
||||
def setActivationPolicy_(self, policy: NSApplicationActivationPolicy) -> bool: ...
|
||||
|
||||
# There are many other operations available but we only actually use copy, so we don't need all of them here.
|
||||
NSCompositingOperationClear: Final = 0
|
||||
NSCompositingOperationCopy: Final = 1
|
||||
|
@ -15,19 +25,19 @@ def NSMakeRect(x: float, y: float, w: float, h: float) -> NSRect: ...
|
|||
class NSImage:
|
||||
@staticmethod
|
||||
def alloc() -> type[NSImage]: ...
|
||||
|
||||
@staticmethod
|
||||
def initByReferencingFile_(file: str) -> NSImage: ...
|
||||
|
||||
@staticmethod
|
||||
def initWithData_(data: bytes) -> NSImage: ...
|
||||
|
||||
@staticmethod
|
||||
def initWithSize_(size: CGSize) -> NSImage: ...
|
||||
|
||||
def size(self) -> CGSize: ...
|
||||
|
||||
def lockFocus(self) -> None: ...
|
||||
def unlockFocus(self) -> None: ...
|
||||
|
||||
def drawInRect_fromRect_operation_fraction_(self, inRect: NSRect, fromRect: NSRect, operation: NSCompositingOperation, fraction: float) -> None: ...
|
||||
def drawInRect_fromRect_operation_fraction_(
|
||||
self,
|
||||
inRect: NSRect,
|
||||
fromRect: NSRect,
|
||||
operation: NSCompositingOperation,
|
||||
fraction: float,
|
||||
) -> None: ...
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue