""" This file is part of the SplendidBear Websites' projects. Copyright (c) 2026 @ www.splendidbear.org For the full copyright and license information, please view the LICENSE file that was distributed with this source code. """ from __future__ import annotations import io import logging import gi gi.require_version("GdkPixbuf", "2.0") gi.require_version("Gst", "1.0") from gi.repository import GdkPixbuf, Gst from mineseeker import config from mineseeker.api import client from mineseeker.constants import IMAGE_NAMES, SOUND_NAMES log = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Image cache { filename: GdkPixbuf.Pixbuf } # --------------------------------------------------------------------------- _images: dict[str, GdkPixbuf.Pixbuf] = {} def load_images(cell_size: int = 40) -> None: """ Fetch all game images from the server and cache them as GdkPixbuf.Pixbuf. Call once at startup (blocking; run in a thread if you want a splash screen). """ for name in IMAGE_NAMES: url = f"{config.BASE_URL}/images/{name}" try: resp = client.get_session().get(url, timeout=10) resp.raise_for_status() loader = GdkPixbuf.PixbufLoader() loader.write(resp.content) loader.close() pixbuf = loader.get_pixbuf() # Scale to the cell size used by the grid widget pixbuf = pixbuf.scale_simple(cell_size, cell_size, GdkPixbuf.InterpType.BILINEAR) _images[name] = pixbuf except Exception as exc: log.warning("Could not load image %s: %s", name, exc) def get_image(name: str) -> GdkPixbuf.Pixbuf | None: """Return a cached Pixbuf by filename, or None if not loaded.""" return _images.get(name) def get_image_or_fallback(name: str, fallback: str) -> GdkPixbuf.Pixbuf | None: return _images.get(name) or _images.get(fallback) # --------------------------------------------------------------------------- # Sound — via GStreamer playbin # --------------------------------------------------------------------------- _sounds: dict[str, str] = {} # { key: URI } def load_sounds() -> None: """ Build the URI map for the six game sound effects. GStreamer will stream them on-demand from the server. """ Gst.init(None) for filename in SOUND_NAMES: key = filename.split(".")[0] # "click", "bomb", etc. _sounds[key] = f"{config.BASE_URL}/sound/{filename}" def play_sound(key: str) -> None: """ Play a sound by key ("click", "mine", "warning", "bomb", "won", "starting"). Each call spawns a fresh GStreamer playbin — fire-and-forget. """ uri = _sounds.get(key) if not uri: return try: player = Gst.ElementFactory.make("playbin", None) if player is None: return player.set_property("uri", uri) player.set_state(Gst.State.PLAYING) # Connect to bus to clean up after playback bus = player.get_bus() bus.add_signal_watch() def _on_message(bus, msg, player=player): if msg.type in (Gst.MessageType.EOS, Gst.MessageType.ERROR): player.set_state(Gst.State.NULL) bus.remove_signal_watch() return True bus.connect("message", _on_message) except Exception as exc: log.debug("Sound play failed (%s): %s", key, exc)