feat(vegas): one declared participation per plugin (scroll | pause | exclude) (#682)

A plugin takes part in Vegas mode in one declared way: 'scroll', 'pause'
or 'exclude', resolved from the user's vegas_participation setting, the
manifest field, then the legacy hooks, so no plugin changes behaviour.
The stream manager decides inclusion and pauses through it; the installed
plugins API and the Vegas plugin-order list report it. Deprecates
get_supported_vegas_modes, get_vegas_segment_width and vegas_panel_count
for removal in 3.9.0, and regenerates docs/DEPRECATIONS_3.8.md to include
them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-30 09:30:37 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b2df0fda1b
commit 1c928b2033
19 changed files with 1094 additions and 244 deletions
+24
View File
@@ -53,3 +53,27 @@ def deprecated(removal: str, alternative: Optional[str] = None) -> Callable[[F],
return wrapper # type: ignore[return-value]
return decorate
def warn_deprecated(what: str, removal: str, alternative: Optional[str] = None,
once_key: Optional[str] = None) -> bool:
"""Warn that ``what`` will be removed in ``removal``, once per process.
For what ``@deprecated`` cannot decorate: a config key, a manifest field,
a value a hook returns. Same message, log line and DeprecationWarning as
the decorator. ``once_key`` (default: ``what``) is what "once" counts
against, so one deprecated key can warn once for each plugin that sets it.
Returns whether this call warned.
"""
message = f"{what} is deprecated and will be removed in LEDMatrix {removal}"
if alternative:
message += f"; {alternative}"
key = once_key or what
with _warned_lock:
if key in _warned:
return False
_warned.add(key)
logger.warning(message)
warnings.warn(message, DeprecationWarning, stacklevel=2)
return True
+255 -58
View File
@@ -13,6 +13,7 @@ from enum import Enum
from typing import Dict, Any, Optional, List
import os
import sys
from src.deprecation import deprecated, warn_deprecated
from src.logging_config import get_logger
@@ -65,27 +66,178 @@ def _fallback_font_manager() -> Any:
class VegasDisplayMode(Enum):
"""
Display mode for Vegas scroll integration.
Legacy display mode for Vegas scroll integration.
Determines how a plugin's content behaves within the continuous scroll:
Superseded by :meth:`BasePlugin.get_vegas_participation`. Vegas still
reads a plugin's :meth:`BasePlugin.get_vegas_display_mode` to derive its
participation when nothing declares one, and only STATIC matters there:
- SCROLL: Content scrolls continuously within the stream.
Best for multi-item plugins like sports scores, odds tickers, news feeds.
Plugin provides multiple frames via get_vegas_content().
- FIXED_SEGMENT: Content is a fixed-width block that scrolls BY with
the rest of the content. Best for static info like clock, weather.
Plugin provides a single image sized to vegas_panel_count panels.
- STATIC: Scroll pauses, plugin displays for its duration, then scroll
resumes. Best for important alerts or detailed views that need attention.
Plugin uses standard display() method during the pause.
- STATIC: the scroll pauses for the plugin's turn and its display() draws
it full screen -- participation ``'pause'``.
- SCROLL and FIXED_SEGMENT: the plugin's content joins the scroll --
participation ``'scroll'``. Vegas has never told the two apart: a card's
width comes from get_vegas_content() and ``vegas_width_pct``, not from
the mode. The distinction is deprecated and goes away in LEDMatrix 3.9.0.
"""
SCROLL = "scroll"
FIXED_SEGMENT = "fixed"
STATIC = "static"
#: How a plugin takes part in Vegas mode (BasePlugin.get_vegas_participation):
#:
#: - ``'scroll'``: its content joins the scrolling strip.
#: - ``'pause'``: the scroll stops when the plugin's turn comes round, and its
#: display() draws it full screen for its display duration.
#: - ``'exclude'``: it is left out of Vegas mode.
VEGAS_PARTICIPATION_VALUES = ('scroll', 'pause', 'exclude')
#: The release that removes get_supported_vegas_modes(),
#: get_vegas_segment_width(), the ``vegas_panel_count`` setting and the
#: SCROLL / FIXED_SEGMENT distinction. The two @deprecated markers below
#: spell it as a literal, because tools that read markers statically (the
#: deprecation tests, the plugin API usage scan) cannot follow a name.
VEGAS_LEGACY_REMOVAL = "3.9.0"
_vegas_logger = get_logger(__name__)
_vegas_warned: set = set()
def _vegas_warn_once(key: Any, message: str, *args: Any) -> None:
"""Log a warning about a plugin's Vegas settings once per process.
Participation is resolved at every rotation refresh, so a bad value would
otherwise log on every one of them.
"""
if key in _vegas_warned:
return
_vegas_warned.add(key)
_vegas_logger.warning(message, *args)
def vegas_participation_value(value: Any) -> Optional[str]:
"""``value`` as one of VEGAS_PARTICIPATION_VALUES, or None if it is not one.
Case and surrounding whitespace are ignored; anything that is not a string
(None, a MagicMock standing in for a plugin in a test) is not a value.
"""
if isinstance(value, str):
value = value.strip().lower()
if value in VEGAS_PARTICIPATION_VALUES:
return value
return None
def configured_vegas_participation(plugin_id: str, config: Any) -> Optional[str]:
"""The user's ``vegas_participation`` setting in a plugin's config, if valid.
An unset or empty value is no setting. Anything else that is not a
participation is logged once and ignored, so the plugin keeps its own.
"""
if not isinstance(config, dict):
return None
raw = config.get('vegas_participation')
if raw is None or (isinstance(raw, str) and not raw.strip()):
return None
value = vegas_participation_value(raw)
if value is None:
_vegas_warn_once(
('config', plugin_id, repr(raw)),
"[%s] Invalid vegas_participation %r, expected one of %s; ignoring it",
plugin_id, raw, ', '.join(VEGAS_PARTICIPATION_VALUES))
return value
def legacy_vegas_participation(plugin: Any) -> str:
"""The participation a plugin's pre-3.8 Vegas hooks describe.
Exactly what Vegas decided from them before participation existed:
1. get_vegas_display_mode() returning ``VegasDisplayMode.STATIC`` pauses,
whatever the content type -- a STATIC plugin whose content type is
``'none'`` was still kept in the rotation to pause it.
2. Otherwise get_vegas_content_type() returning ``'none'`` excludes.
3. Everything else scrolls. SCROLL and FIXED_SEGMENT were never told
apart, and neither were content types ``'multi'``, ``'static'`` or any
other string.
Only the enum member counts as STATIC (a plugin returning the string
``'static'`` never paused), and a hook that raises or is missing counts as
not STATIC and as content type ``'static'``.
"""
display_mode = None
get_mode = getattr(plugin, 'get_vegas_display_mode', None)
if get_mode is not None:
try:
display_mode = get_mode()
except Exception:
_vegas_logger.debug("get_vegas_display_mode() failed on %s; not pausing",
type(plugin).__name__, exc_info=True)
if display_mode == VegasDisplayMode.STATIC:
return 'pause'
content_type = 'static'
get_type = getattr(plugin, 'get_vegas_content_type', None)
if get_type is not None:
try:
content_type = get_type()
except Exception:
_vegas_logger.debug("get_vegas_content_type() failed on %s; treating as 'static'",
type(plugin).__name__, exc_info=True)
if content_type == 'none':
return 'exclude'
return 'scroll'
def resolve_vegas_participation(plugin: Any, plugin_id: Optional[str] = None) -> str:
"""How Vegas mode treats ``plugin``: ``'scroll'``, ``'pause'`` or ``'exclude'``.
What the core calls, rather than the plugin's own
get_vegas_participation(), so the user's setting wins even over a plugin
that overrides that method, and so a plugin that is not a BasePlugin (or a
test double) still gets the legacy derivation:
1. the user's ``vegas_participation`` in the plugin's config;
2. the plugin's get_vegas_participation(), when it returns a valid value
(BasePlugin's reads the manifest's ``vegas_participation``, then
derives one from the legacy hooks);
3. legacy_vegas_participation().
Also where the deprecated ``vegas_panel_count`` setting is reported, once
per plugin. Never raises.
"""
pid = plugin_id or getattr(plugin, 'plugin_id', None) or type(plugin).__name__
config = getattr(plugin, 'config', None)
if isinstance(config, dict) and 'vegas_panel_count' in config:
warn_deprecated(
f"The vegas_panel_count setting (plugin '{pid}')", VEGAS_LEGACY_REMOVAL,
"it has no effect -- use vegas_width_pct to size the plugin's card",
once_key=f"vegas_panel_count:{pid}")
configured = configured_vegas_participation(pid, config)
if configured is not None:
return configured
getter = getattr(plugin, 'get_vegas_participation', None)
if callable(getter):
try:
declared = getter()
except Exception:
_vegas_logger.exception("[%s] get_vegas_participation() failed; "
"using its legacy Vegas hooks", pid)
declared = None
value = vegas_participation_value(declared)
if value is not None:
return value
if isinstance(declared, str):
_vegas_warn_once(
('declared', pid, declared),
"[%s] get_vegas_participation() returned %r, expected one of %s; "
"using its legacy Vegas hooks",
pid, declared, ', '.join(VEGAS_PARTICIPATION_VALUES))
return legacy_vegas_participation(plugin)
class BasePlugin(ABC):
"""
Base class that all plugins must inherit from.
@@ -834,41 +986,97 @@ class BasePlugin(ABC):
"""
return None
def get_vegas_participation(self) -> str:
"""
How this plugin takes part in Vegas mode: ``'scroll'``, ``'pause'`` or
``'exclude'``.
- ``'scroll'``: the plugin's content (get_vegas_content()) joins the
scrolling strip.
- ``'pause'``: the scroll stops when the plugin's turn comes round, and
its display() draws it full screen for get_display_duration().
- ``'exclude'``: the plugin is left out of Vegas mode.
Resolved in this order:
1. the user's ``vegas_participation`` setting in this plugin's config
(the web UI's per-plugin override);
2. ``vegas_participation`` in the plugin's manifest.json -- the way a
plugin declares its own default;
3. derived from the legacy hooks, so a plugin written before this
method existed keeps the behaviour it had: get_vegas_display_mode()
returning ``VegasDisplayMode.STATIC`` pauses, otherwise
get_vegas_content_type() returning ``'none'`` excludes, and
everything else scrolls.
Declare a fixed participation in the manifest rather than overriding
this. Override it only when the answer depends on state -- pause only
while an alert is live, exclude while there is nothing to show. Vegas
applies the user's setting before calling an override, so an override
need not check it.
Returns:
One of VEGAS_PARTICIPATION_VALUES.
Example:
def get_vegas_participation(self):
return 'pause' if self._alert_is_live() else 'scroll'
"""
configured = configured_vegas_participation(self.plugin_id, self.config)
if configured is not None:
return configured
manifest_default = self._manifest_vegas_participation()
if manifest_default is not None:
return manifest_default
return legacy_vegas_participation(self)
def _manifest_vegas_participation(self) -> Optional[str]:
"""``vegas_participation`` from this plugin's manifest, if valid."""
manifests = getattr(self.plugin_manager, 'plugin_manifests', None)
manifest = manifests.get(self.plugin_id) if isinstance(manifests, dict) else None
if not isinstance(manifest, dict) or manifest.get('vegas_participation') is None:
return None
raw = manifest['vegas_participation']
value = vegas_participation_value(raw)
if value is None:
_vegas_warn_once(
('manifest', self.plugin_id, repr(raw)),
"[%s] manifest vegas_participation %r is not one of %s; ignoring it",
self.plugin_id, raw, ', '.join(VEGAS_PARTICIPATION_VALUES))
return value
def get_vegas_content_type(self) -> str:
"""
Indicate the type of content this plugin provides for Vegas scroll.
Legacy: the type of content this plugin provides for Vegas scroll.
Override this to specify how Vegas mode should treat this plugin's content.
Superseded by get_vegas_participation(). Vegas reads it only to derive
a participation when neither the user nor the manifest declares one,
and only ``'none'`` matters there: it excludes the plugin (unless
get_vegas_display_mode() says STATIC). Every other value scrolls.
Returns:
'multi' - Plugin has multiple scrollable items (sports, odds, news)
'static' - Plugin is a static block (clock, weather, music)
'none' - Plugin should not appear in Vegas scroll mode
Example:
def get_vegas_content_type(self):
return 'multi' # We have multiple games to scroll
"""
return 'static'
def get_vegas_display_mode(self) -> VegasDisplayMode:
"""
Get the display mode for Vegas scroll integration.
Legacy: the display mode for Vegas scroll integration.
This method determines how the plugin's content behaves within Vegas mode:
- SCROLL: Content scrolls continuously (multi-item plugins)
- FIXED_SEGMENT: Fixed block that scrolls by (clock, weather)
- STATIC: Pause scroll to display (alerts, detailed views)
Superseded by get_vegas_participation(). Vegas reads it only to derive
a participation when neither the user nor the manifest declares one,
and only STATIC matters there: it pauses the scroll for the plugin's
turn. SCROLL and FIXED_SEGMENT both scroll -- Vegas has never told them
apart, and the distinction is deprecated (removed in 3.9.0).
Override to change default behavior. By default, reads from config
or maps legacy get_vegas_content_type() for backward compatibility.
Reads the plugin's ``vegas_mode`` config value, else maps
get_vegas_content_type() ('multi' to SCROLL, anything else to
FIXED_SEGMENT).
Returns:
VegasDisplayMode enum value
Example:
def get_vegas_display_mode(self):
return VegasDisplayMode.SCROLL
"""
# Check for explicit config setting first
config_mode = self.config.get("vegas_mode")
@@ -888,13 +1096,16 @@ class BasePlugin(ABC):
return VegasDisplayMode.SCROLL
return VegasDisplayMode.FIXED_SEGMENT
@deprecated("3.9.0",
"nothing reads it -- declare vegas_participation in the manifest instead")
def get_supported_vegas_modes(self) -> List[VegasDisplayMode]:
"""
Return list of Vegas display modes this plugin supports.
Deprecated: the Vegas display modes this plugin supports.
Not currently consulted by core: neither Vegas mode nor the web UI
calls it. It is kept, and plugins override it, as the declared set of
modes a future mode picker would offer.
Never consulted by core -- neither Vegas mode nor the web UI calls it
-- and removed in LEDMatrix 3.9.0. A plugin's own override keeps
working for the plugin itself; calling this base implementation logs a
deprecation warning.
By default:
- 'multi' content type plugins support SCROLL and FIXED_SEGMENT
@@ -903,11 +1114,6 @@ class BasePlugin(ABC):
Returns:
List of VegasDisplayMode values this plugin can use
Example:
def get_supported_vegas_modes(self):
# This plugin only makes sense as a scrolling ticker
return [VegasDisplayMode.SCROLL]
"""
content_type = self.get_vegas_content_type()
@@ -918,30 +1124,21 @@ class BasePlugin(ABC):
else: # 'static'
return [VegasDisplayMode.FIXED_SEGMENT, VegasDisplayMode.STATIC]
@deprecated("3.9.0",
"nothing reads it -- Vegas sizes a card from vegas_width_pct "
"(see get_vegas_render_width())")
def get_vegas_segment_width(self) -> Optional[int]:
"""
Get the preferred width for this plugin in Vegas FIXED_SEGMENT mode.
Deprecated: the number of panels this plugin wanted as a FIXED_SEGMENT.
Not currently consulted by core: Vegas mode sizes a card from the
``vegas_width_pct`` / ``vegas_scroll.render_width_pct`` settings
(see get_vegas_render_width()). Kept because plugins override it.
Returns the number of panels this plugin should occupy when displayed
as a fixed segment. The actual pixel width is calculated as:
width = panels * single_panel_width
Where single_panel_width comes from display.hardware.cols in config.
Override to provide dynamic sizing based on content.
Returns None to use the default (1 panel).
Never consulted by core: Vegas sizes a card from the
``vegas_width_pct`` / ``vegas_scroll.render_width_pct`` settings (see
get_vegas_render_width()). Removed, with the ``vegas_panel_count``
setting it reads, in LEDMatrix 3.9.0.
Returns:
Number of panels, or None for default (1 panel)
Example:
def get_vegas_segment_width(self):
# Clock needs 2 panels to show time clearly
return 2
``vegas_panel_count`` from config when it is a positive integer,
else None
"""
raw_value = self.config.get("vegas_panel_count", None)
if raw_value is None:
+2 -1
View File
@@ -569,7 +569,8 @@ class PluginManager:
#: prefix rule would silently stop validating it.
#:
#: Read by: ``vegas_mode/plugin_adapter.py`` (``vegas_width_pct``,
#: ``vegas_overflow``) and ``base_plugin.py`` (``vegas_max_width_screens``).
#: ``vegas_overflow``) and ``base_plugin.py`` (``vegas_max_width_screens``,
#: ``vegas_participation``).
#:
#: The list itself lives with the other core-owned per-plugin properties in
#: ``schema_manager.CORE_PLUGIN_PROPERTIES``, which the web save path also
+20 -2
View File
@@ -127,8 +127,9 @@ CORE_PLUGIN_PROPERTIES: Dict[str, Dict[str, Any]] = {
"description": "Enable live priority takeover when plugin has live content"
},
# Vegas tuning read by vegas_mode/plugin_adapter.py and base_plugin.py.
# Left untyped: the adapter validates them itself and ignores a bad
# value with a log line, so a stored one must never block a save.
# These three are left untyped: the adapter validates them itself and
# ignores a bad value with a log line, so a stored one must never block a
# save.
"vegas_width_pct": {
"description": "Vegas mode: width of this plugin's card, as a percentage of the panel"
},
@@ -138,6 +139,22 @@ CORE_PLUGIN_PROPERTIES: Dict[str, Dict[str, Any]] = {
"vegas_max_width_screens": {
"description": "Vegas mode: widest this plugin's card may be, in screens"
},
# Read by resolve_vegas_participation / BasePlugin.get_vegas_participation.
# An enum with no default: a default would be written into every plugin's
# config and override the participation the plugin itself declares.
"vegas_participation": {
"type": "string",
"enum": ["scroll", "pause", "exclude"],
"title": "Vegas participation",
"description": (
"Vegas mode: how this plugin takes part in the scrolling ticker. "
"'scroll' = its content scrolls by with everything else; "
"'pause' = the ticker stops for this plugin's turn and shows it "
"full screen for its display duration; "
"'exclude' = leave it out of Vegas mode. "
"Leave unset to use the plugin's own default."
),
},
}
#: The keys of CORE_PLUGIN_PROPERTIES that are Vegas tuning rather than plugin
@@ -145,6 +162,7 @@ CORE_PLUGIN_PROPERTIES: Dict[str, Dict[str, Any]] = {
#: PluginManager.CORE_OWNED_CONFIG_KEYS).
CORE_VEGAS_TUNING_KEYS = frozenset({
'vegas_width_pct', 'vegas_overflow', 'vegas_max_width_screens',
'vegas_participation',
})
+6 -4
View File
@@ -5,10 +5,12 @@ Main orchestrator for Vegas-style continuous scroll mode. Coordinates between
StreamManager, RenderPipeline, and the display system to provide smooth
continuous scrolling of all enabled plugin content.
Supports three display modes per plugin:
- SCROLL: Content scrolls continuously within the stream
- FIXED_SEGMENT: Fixed block that scrolls by with other content
- STATIC: Scroll pauses, plugin displays for its duration, then resumes
Each plugin takes part in one of three ways (its Vegas participation, see
BasePlugin.get_vegas_participation):
- 'scroll': its content scrolls by within the stream
- 'pause': the scroll pauses, the plugin displays for its duration, then
the scroll resumes
- 'exclude': left out
"""
import logging
-23
View File
@@ -1369,29 +1369,6 @@ class PluginAdapter:
logger.debug("[%s] Cleared plugin scroll cache", plugin_id)
return cleared
def get_content_type(self, plugin: 'BasePlugin', plugin_id: str) -> str:
"""
Get the type of content a plugin provides.
Args:
plugin: Plugin instance
plugin_id: Plugin identifier
Returns:
'multi' for multiple items, 'static' for single frame, 'none' for excluded
"""
if hasattr(plugin, 'get_vegas_content_type'):
try:
return plugin.get_vegas_content_type()
except (AttributeError, TypeError, ValueError):
logger.exception(
"Error calling get_vegas_content_type() on %s",
plugin_id
)
# Default to static for plugins without explicit type
return 'static'
def cleanup(self) -> None:
"""Clean up resources."""
with self._cache_lock:
+31 -50
View File
@@ -5,23 +5,25 @@ Manages plugin content streaming with look-ahead buffering. Maintains a queue
of plugin content that's ready to be rendered, prefetching 1-2 plugins ahead
of the current scroll position.
Supports three display modes:
- SCROLL: Continuous scrolling content
- FIXED_SEGMENT: Fixed block that scrolls by
- STATIC: Pause scroll to display (marked for coordinator handling)
Each plugin takes part in one of three ways (its Vegas participation, see
BasePlugin.get_vegas_participation):
- 'scroll': its content joins the strip
- 'pause': the scroll pauses for its turn (a STATIC segment, marked for the
coordinator)
- 'exclude': left out of the rotation
"""
import logging
import threading
import time
from typing import Optional, List, Dict, Any, Deque, Tuple, TYPE_CHECKING, cast
from typing import Optional, List, Dict, Any, Deque, Tuple, TYPE_CHECKING
from collections import deque
from dataclasses import dataclass, field
from PIL import Image
from src.vegas_mode.config import VegasModeConfig
from src.vegas_mode.plugin_adapter import PluginAdapter
from src.plugin_system.base_plugin import VegasDisplayMode
from src.plugin_system.base_plugin import VegasDisplayMode, resolve_vegas_participation
if TYPE_CHECKING:
from src.plugin_system.plugin_manager import PluginManager
@@ -34,11 +36,12 @@ class ContentSegment:
"""One plugin's content for a cycle.
A STATIC segment carries no images: it marks where the coordinator pauses
the scroll to show the plugin full-screen.
the scroll to show a plugin whose participation is ``'pause'``. Every
other segment is SCROLL.
"""
plugin_id: str
images: List[Image.Image]
display_mode: VegasDisplayMode = field(default=VegasDisplayMode.FIXED_SEGMENT)
display_mode: VegasDisplayMode = field(default=VegasDisplayMode.SCROLL)
class StreamManager:
@@ -335,25 +338,14 @@ class StreamManager:
logger.debug("[%s] Vegas: skipped (not enabled)", plugin_id)
continue
# Content type 'none' is left out, except for STATIC plugins,
# which pause the scroll rather than contributing to it.
content_type = self.plugin_adapter.get_content_type(plugin, plugin_id)
display_mode = VegasDisplayMode.FIXED_SEGMENT
try:
display_mode = plugin.get_vegas_display_mode()
except Exception:
# Plugin error should not abort refresh; use default mode
logger.exception(
"[%s] (%s) get_vegas_display_mode() failed, using default",
plugin_id, plugin.__class__.__name__
)
included = (content_type != 'none'
or display_mode == VegasDisplayMode.STATIC)
# 'pause' plugins stay in the rotation: they pause the scroll
# for their turn rather than contributing to it.
participation = resolve_vegas_participation(plugin, plugin_id)
included = participation != 'exclude'
logger.debug(
"[%s] Vegas: %s (content_type=%s, display_mode=%s)",
"[%s] Vegas: %s (participation=%s)",
plugin_id, "included" if included else "excluded",
content_type, display_mode.value
participation
)
if included:
available_plugins.append(plugin_id)
@@ -580,23 +572,13 @@ class StreamManager:
logger.warning("[%s] Plugin not found in plugin_manager.plugins", plugin_id)
return None
display_mode = VegasDisplayMode.FIXED_SEGMENT
try:
display_mode = plugin.get_vegas_display_mode()
except (AttributeError, TypeError) as e:
logger.debug(
"[%s] get_vegas_display_mode() not available: %s (using FIXED_SEGMENT)",
plugin_id, e
)
# For STATIC mode, we create a placeholder segment
# The actual content will be displayed by coordinator during pause
if display_mode == VegasDisplayMode.STATIC:
# Create minimal placeholder - coordinator handles actual display
# A 'pause' plugin gets a placeholder segment; the coordinator
# draws it with display() when the scroll reaches its turn.
if resolve_vegas_participation(plugin, plugin_id) == 'pause':
segment = ContentSegment(
plugin_id=plugin_id,
images=[], # No images needed for static pause
display_mode=display_mode
display_mode=VegasDisplayMode.STATIC
)
self.stats['segments_fetched'] += 1
logger.debug(
@@ -605,7 +587,6 @@ class StreamManager:
)
return segment
# Get content via adapter for SCROLL/FIXED_SEGMENT modes
images = self.plugin_adapter.get_content(plugin, plugin_id)
if not images:
# The adapter already warns when every content path failed;
@@ -619,13 +600,13 @@ class StreamManager:
segment = ContentSegment(
plugin_id=plugin_id,
images=images,
display_mode=display_mode
display_mode=VegasDisplayMode.SCROLL
)
self.stats['segments_fetched'] += 1
logger.debug(
"[%s] Segment: %d image(s), %dpx, mode=%s",
plugin_id, len(images), total_width, display_mode.value
"[%s] Segment: %d image(s), %dpx",
plugin_id, len(images), total_width
)
return segment
@@ -693,16 +674,16 @@ class StreamManager:
return layout
def is_static_plugin(self, plugin_id: str) -> bool:
"""Whether a loaded plugin asks Vegas to pause for it (STATIC mode)."""
"""Whether a loaded plugin asks Vegas to pause for it (participation 'pause').
Only 'pause' is acted on here. A plugin whose participation has turned
to 'exclude' since the rotation was built is still fetched this cycle,
as it always was; the next refresh drops it.
"""
plugin = getattr(self.plugin_manager, 'plugins', {}).get(plugin_id)
if plugin is None:
return False
try:
return cast(bool, plugin.get_vegas_display_mode() == VegasDisplayMode.STATIC)
except Exception:
logger.debug("[%s] get_vegas_display_mode() failed; treating as not STATIC",
plugin_id, exc_info=True)
return False
return resolve_vegas_participation(plugin, plugin_id) == 'pause'
def take_next_group(
self, count: Optional[int] = None, offscreen_only: bool = False