109 lines
3.4 KiB
Python
109 lines
3.4 KiB
Python
|
|
"""
|
||
|
|
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)
|