jmri_server.services.audio
AudioService — JSON topic audio.
Wire shape per jmri/server/json/audio/audio-server.json and the
JMRI Java handler JsonAudioHttpService (case 16 STOPPED, case 17
PLAYING, command = Play / Stop on post).
1"""``AudioService`` — JSON topic ``audio``. 2 3Wire shape per ``jmri/server/json/audio/audio-server.json`` and the 4JMRI Java handler ``JsonAudioHttpService`` (case 16 STOPPED, case 17 5PLAYING, ``command`` = Play / Stop on post). 6""" 7from __future__ import annotations 8 9from typing import Any 10 11from jmri_core.beans.audio import ( 12 AUDIO_STATE_PLAYING, 13 AUDIO_STATE_STOPPED, 14 Audio, 15 AudioManager, 16) 17 18from jmri_server.service import JsonServiceError 19from jmri_server.services.beans import _BeanService, _common_named_bean 20 21 22class AudioService(_BeanService): 23 topic = "audio" 24 25 def manager(self) -> AudioManager: 26 return self.ctx.connection.audios 27 28 def project(self, bean: Audio) -> dict[str, Any]: 29 return _common_named_bean(bean) | { 30 "state": bean.state, 31 "kind": bean.kind, 32 "url": bean.url, 33 "gain": bean.gain, 34 } 35 36 async def apply(self, bean: Audio, data: dict[str, Any]) -> None: 37 if "kind" in data: 38 kind = data["kind"] 39 if kind not in ("source", "buffer", "listener"): 40 raise JsonServiceError(400, f"invalid audio kind: {kind!r}") 41 bean.kind = kind 42 if "url" in data: 43 bean.url = str(data["url"]) if data["url"] is not None else None 44 if "gain" in data: 45 try: 46 gain = float(data["gain"]) 47 except (TypeError, ValueError): 48 raise JsonServiceError(400, "audio.gain must be numeric") from None 49 if not 0.0 <= gain <= 1.0: 50 raise JsonServiceError(400, "audio.gain must be in [0.0, 1.0]") 51 bean.gain = gain 52 # Explicit state override (rare — clients normally send 53 # ``command`` instead) but scripts sometimes set state directly. 54 if "state" in data: 55 state = int(data["state"]) 56 if state not in (AUDIO_STATE_STOPPED, AUDIO_STATE_PLAYING): 57 raise JsonServiceError( 58 400, 59 f"audio.state must be {AUDIO_STATE_STOPPED} (stopped) or " 60 f"{AUDIO_STATE_PLAYING} (playing)", 61 ) 62 if state == AUDIO_STATE_PLAYING: 63 await bean.play() 64 else: 65 await bean.stop() 66 # JMRI Java's Play / Stop verbs. Only meaningful on sources; a 67 # buffer or listener silently no-ops the command rather than 68 # 400-ing to keep the API forgiving for polymorphic clients. 69 command = data.get("command") 70 if command == "Play": 71 if bean.kind == "source": 72 await bean.play() 73 elif command == "Stop": 74 if bean.kind == "source": 75 await bean.stop() 76 elif command is not None: 77 raise JsonServiceError(400, f"unknown audio command: {command!r}")
23class AudioService(_BeanService): 24 topic = "audio" 25 26 def manager(self) -> AudioManager: 27 return self.ctx.connection.audios 28 29 def project(self, bean: Audio) -> dict[str, Any]: 30 return _common_named_bean(bean) | { 31 "state": bean.state, 32 "kind": bean.kind, 33 "url": bean.url, 34 "gain": bean.gain, 35 } 36 37 async def apply(self, bean: Audio, data: dict[str, Any]) -> None: 38 if "kind" in data: 39 kind = data["kind"] 40 if kind not in ("source", "buffer", "listener"): 41 raise JsonServiceError(400, f"invalid audio kind: {kind!r}") 42 bean.kind = kind 43 if "url" in data: 44 bean.url = str(data["url"]) if data["url"] is not None else None 45 if "gain" in data: 46 try: 47 gain = float(data["gain"]) 48 except (TypeError, ValueError): 49 raise JsonServiceError(400, "audio.gain must be numeric") from None 50 if not 0.0 <= gain <= 1.0: 51 raise JsonServiceError(400, "audio.gain must be in [0.0, 1.0]") 52 bean.gain = gain 53 # Explicit state override (rare — clients normally send 54 # ``command`` instead) but scripts sometimes set state directly. 55 if "state" in data: 56 state = int(data["state"]) 57 if state not in (AUDIO_STATE_STOPPED, AUDIO_STATE_PLAYING): 58 raise JsonServiceError( 59 400, 60 f"audio.state must be {AUDIO_STATE_STOPPED} (stopped) or " 61 f"{AUDIO_STATE_PLAYING} (playing)", 62 ) 63 if state == AUDIO_STATE_PLAYING: 64 await bean.play() 65 else: 66 await bean.stop() 67 # JMRI Java's Play / Stop verbs. Only meaningful on sources; a 68 # buffer or listener silently no-ops the command rather than 69 # 400-ing to keep the API forgiving for polymorphic clients. 70 command = data.get("command") 71 if command == "Play": 72 if bean.kind == "source": 73 await bean.play() 74 elif command == "Stop": 75 if bean.kind == "source": 76 await bean.stop() 77 elif command is not None: 78 raise JsonServiceError(400, f"unknown audio command: {command!r}")
Base class encapsulating get/list/put/post/delete plumbing.
Concrete bean services set topic and override manager(),
project(), and (when mutations are supported) apply().
37 async def apply(self, bean: Audio, data: dict[str, Any]) -> None: 38 if "kind" in data: 39 kind = data["kind"] 40 if kind not in ("source", "buffer", "listener"): 41 raise JsonServiceError(400, f"invalid audio kind: {kind!r}") 42 bean.kind = kind 43 if "url" in data: 44 bean.url = str(data["url"]) if data["url"] is not None else None 45 if "gain" in data: 46 try: 47 gain = float(data["gain"]) 48 except (TypeError, ValueError): 49 raise JsonServiceError(400, "audio.gain must be numeric") from None 50 if not 0.0 <= gain <= 1.0: 51 raise JsonServiceError(400, "audio.gain must be in [0.0, 1.0]") 52 bean.gain = gain 53 # Explicit state override (rare — clients normally send 54 # ``command`` instead) but scripts sometimes set state directly. 55 if "state" in data: 56 state = int(data["state"]) 57 if state not in (AUDIO_STATE_STOPPED, AUDIO_STATE_PLAYING): 58 raise JsonServiceError( 59 400, 60 f"audio.state must be {AUDIO_STATE_STOPPED} (stopped) or " 61 f"{AUDIO_STATE_PLAYING} (playing)", 62 ) 63 if state == AUDIO_STATE_PLAYING: 64 await bean.play() 65 else: 66 await bean.stop() 67 # JMRI Java's Play / Stop verbs. Only meaningful on sources; a 68 # buffer or listener silently no-ops the command rather than 69 # 400-ing to keep the API forgiving for polymorphic clients. 70 command = data.get("command") 71 if command == "Play": 72 if bean.kind == "source": 73 await bean.play() 74 elif command == "Stop": 75 if bean.kind == "source": 76 await bean.stop() 77 elif command is not None: 78 raise JsonServiceError(400, f"unknown audio command: {command!r}")
Apply mutations from a client request. Default: no-op.