Start intro managed

parent e9e2f6c9
...@@ -27,11 +27,52 @@ class GamesThread(ThreadedComponent): ...@@ -27,11 +27,52 @@ class GamesThread(ThreadedComponent):
self._shutdown_event = threading.Event() self._shutdown_event = threading.Event()
self.message_queue = None self.message_queue = None
def _cleanup_stale_ingame_matches(self):
"""Clean up any stale 'ingame' matches from previous crashed sessions"""
try:
session = self.db_manager.get_session()
try:
# Get today's date
today = datetime.now().date()
# Find all ingame matches from today that might be stale
stale_matches = session.query(MatchModel).filter(
MatchModel.start_time.isnot(None),
MatchModel.start_time >= datetime.combine(today, datetime.min.time()),
MatchModel.start_time < datetime.combine(today, datetime.max.time()),
MatchModel.status == 'ingame',
MatchModel.active_status == True
).all()
if not stale_matches:
logger.info("No stale ingame matches found")
return
logger.info(f"Found {len(stale_matches)} stale ingame matches - cleaning up")
# Change status to pending and set active_status to False
for match in stale_matches:
logger.info(f"Cleaning up stale match {match.match_number}: {match.fighter1_township} vs {match.fighter2_township}")
match.status = 'pending'
match.active_status = False
session.commit()
logger.info(f"Cleaned up {len(stale_matches)} stale ingame matches")
finally:
session.close()
except Exception as e:
logger.error(f"Failed to cleanup stale ingame matches: {e}")
def initialize(self) -> bool: def initialize(self) -> bool:
"""Initialize the games thread""" """Initialize the games thread"""
try: try:
logger.info("Initializing GamesThread...") logger.info("Initializing GamesThread...")
# Clean up any stale 'ingame' matches from previous crashed sessions
self._cleanup_stale_ingame_matches()
# Register with message bus first # Register with message bus first
self.message_queue = self.message_bus.register_component(self.name) self.message_queue = self.message_bus.register_component(self.name)
...@@ -274,6 +315,25 @@ class GamesThread(ThreadedComponent): ...@@ -274,6 +315,25 @@ class GamesThread(ThreadedComponent):
# Determine current game status # Determine current game status
game_status = self._determine_game_status() game_status = self._determine_game_status()
# If status is "already_active" but game is not active, activate the fixture
if game_status == "already_active" and not self.game_active:
logger.info("Status is 'already_active' but game is not active - activating fixture")
active_fixture = self._find_active_today_fixture()
if active_fixture:
# Create a dummy message for activation
dummy_message = Message(
type=MessageType.START_GAME,
sender=message.sender,
recipient=self.name,
data={"timestamp": time.time()},
correlation_id=message.correlation_id
)
self._activate_fixture(active_fixture, dummy_message)
# Update status after activation
game_status = "started"
else:
logger.warning("Could not find active fixture to activate")
# Send GAME_STATUS response back to the requester # Send GAME_STATUS response back to the requester
response = Message( response = Message(
type=MessageType.GAME_STATUS, type=MessageType.GAME_STATUS,
...@@ -548,9 +608,9 @@ class GamesThread(ThreadedComponent): ...@@ -548,9 +608,9 @@ class GamesThread(ThreadedComponent):
timer_running = self._is_timer_running_for_fixture(fixture_id) timer_running = self._is_timer_running_for_fixture(fixture_id)
if not timer_running: if not timer_running:
# Timer not running, change status to failed # Timer not running, change status to pending and set active_status to False
logger.info(f"Timer not running for fixture {fixture_id}, changing ingame matches to failed") logger.info(f"Timer not running for fixture {fixture_id}, changing ingame matches to pending and setting active_status to False")
self._change_fixture_matches_status(fixture_id, 'ingame', 'failed') self._change_fixture_matches_status(fixture_id, 'ingame', 'pending', active_status_to_set=False)
# Check if this was the only non-terminal fixture # Check if this was the only non-terminal fixture
if self._is_only_non_terminal_fixture(fixture_id): if self._is_only_non_terminal_fixture(fixture_id):
...@@ -593,7 +653,7 @@ class GamesThread(ThreadedComponent): ...@@ -593,7 +653,7 @@ class GamesThread(ThreadedComponent):
# In a real implementation, you'd check the match_timer component status # In a real implementation, you'd check the match_timer component status
return self.current_fixture_id == fixture_id and self.game_active return self.current_fixture_id == fixture_id and self.game_active
def _change_fixture_matches_status(self, fixture_id: str, from_status: str, to_status: str): def _change_fixture_matches_status(self, fixture_id: str, from_status: str, to_status: str, active_status_to_set: Optional[bool] = None):
"""Change status of matches in a fixture from one status to another""" """Change status of matches in a fixture from one status to another"""
try: try:
session = self.db_manager.get_session() session = self.db_manager.get_session()
...@@ -607,6 +667,9 @@ class GamesThread(ThreadedComponent): ...@@ -607,6 +667,9 @@ class GamesThread(ThreadedComponent):
for match in matches: for match in matches:
logger.info(f"Changing match {match.match_number} status from {from_status} to {to_status}") logger.info(f"Changing match {match.match_number} status from {from_status} to {to_status}")
match.status = to_status match.status = to_status
if active_status_to_set is not None:
match.active_status = active_status_to_set
logger.info(f"Setting active_status to {active_status_to_set} for match {match.match_number}")
session.commit() session.commit()
......
...@@ -387,6 +387,24 @@ class OverlayWebView(QWebEngineView): ...@@ -387,6 +387,24 @@ class OverlayWebView(QWebEngineView):
if page: if page:
logger.debug(f"GREEN SCREEN DEBUG: Page background color before load: {page.backgroundColor()}") logger.debug(f"GREEN SCREEN DEBUG: Page background color before load: {page.backgroundColor()}")
# CRITICAL FIX: Add safety check for PyInstaller bundle template access
import sys
if getattr(sys, 'frozen', False) and template_source == "builtin":
# Running in PyInstaller bundle - verify template path is accessible
try:
# Test if we can read the template file
with open(template_path, 'r', encoding='utf-8') as f:
content = f.read()
if len(content) == 0:
logger.warning(f"Template file {template_path} is empty, using fallback")
self._load_fallback_overlay()
return
except Exception as file_error:
logger.error(f"Cannot read template file {template_path} in bundle: {file_error}")
logger.warning("Using fallback overlay due to template access issue")
self._load_fallback_overlay()
return
self.load(QUrl.fromLocalFile(str(template_path))) self.load(QUrl.fromLocalFile(str(template_path)))
self.current_template = template_name self.current_template = template_name
...@@ -1433,16 +1451,28 @@ class PlayerWindow(QMainWindow): ...@@ -1433,16 +1451,28 @@ class PlayerWindow(QMainWindow):
if video_widget: if video_widget:
logger.debug(f"GREEN SCREEN FIX: Video widget state before template load - visible: {video_widget.isVisible()}") logger.debug(f"GREEN SCREEN FIX: Video widget state before template load - visible: {video_widget.isVisible()}")
# Load specified template or reload current template when playing a video # DELAYED TEMPLATE LOADING: Load template AFTER video starts to avoid timing conflicts
# Template loading during video initialization can cause crashes in PyInstaller bundles
def load_template_after_video_start():
"""Load template after video has started playing to avoid timing issues"""
try:
if hasattr(self, 'window_overlay') and isinstance(self.window_overlay, OverlayWebView): if hasattr(self, 'window_overlay') and isinstance(self.window_overlay, OverlayWebView):
if template_name: if template_name:
logger.debug(f"GREEN SCREEN FIX: Loading template while protecting video context: {template_name}") logger.debug(f"GREEN SCREEN FIX: Loading template after video start: {template_name}")
# Load template after video starts to avoid conflicts
self.window_overlay.load_template(template_name) self.window_overlay.load_template(template_name)
logger.info(f"Loaded template '{template_name}' for video playback") logger.info(f"Loaded template '{template_name}' for video playback")
else: else:
logger.debug(f"GREEN SCREEN FIX: Reloading template while protecting video context") logger.debug(f"GREEN SCREEN FIX: Reloading template after video start")
# Reload template after video starts to avoid conflicts
self.window_overlay.reload_current_template() self.window_overlay.reload_current_template()
logger.info("Reloaded current overlay template for video playback") logger.info("Reloaded current overlay template for video playback")
except Exception as template_error:
logger.error(f"Failed to load template after video start: {template_error}")
# Continue without template - video should still play
# Schedule template loading to happen after video has started (2 seconds delay)
QTimer.singleShot(2000, load_template_after_video_start)
# CRITICAL FIX: Force video widget refresh after template operations # CRITICAL FIX: Force video widget refresh after template operations
if video_widget: if video_widget:
...@@ -3096,6 +3126,8 @@ class QtVideoPlayer(QObject): ...@@ -3096,6 +3126,8 @@ class QtVideoPlayer(QObject):
def _find_intro_video_file(self, match_id: int) -> Optional[Path]: def _find_intro_video_file(self, match_id: int) -> Optional[Path]:
"""Find the correct intro video file based on priority""" """Find the correct intro video file based on priority"""
try: try:
logger.debug(f"DEBUG: Searching for intro video file for match_id: {match_id}")
# Priority 1: Check for INTRO.mp4 in the unzipped ZIP file of the match # Priority 1: Check for INTRO.mp4 in the unzipped ZIP file of the match
if match_id: if match_id:
from ..database.manager import DatabaseManager from ..database.manager import DatabaseManager
...@@ -3110,10 +3142,12 @@ class QtVideoPlayer(QObject): ...@@ -3110,10 +3142,12 @@ class QtVideoPlayer(QObject):
import tempfile import tempfile
import os import os
temp_base = Path(tempfile.gettempdir()) temp_base = Path(tempfile.gettempdir())
logger.debug(f"DEBUG: Looking for match temp dirs in: {temp_base} with pattern: {temp_dir_pattern}")
for temp_dir in temp_base.glob(f"{temp_dir_pattern}*"): for temp_dir in temp_base.glob(f"{temp_dir_pattern}*"):
if temp_dir.is_dir(): if temp_dir.is_dir():
intro_file = temp_dir / "INTRO.mp4" intro_file = temp_dir / "INTRO.mp4"
logger.debug(f"DEBUG: Checking for INTRO.mp4 in match ZIP: {intro_file}")
if intro_file.exists(): if intro_file.exists():
logger.info(f"Found INTRO.mp4 in match ZIP: {intro_file}") logger.info(f"Found INTRO.mp4 in match ZIP: {intro_file}")
return intro_file return intro_file
...@@ -3121,10 +3155,14 @@ class QtVideoPlayer(QObject): ...@@ -3121,10 +3155,14 @@ class QtVideoPlayer(QObject):
# Priority 2: Check for uploaded intro video # Priority 2: Check for uploaded intro video
# This would need to be implemented based on how uploaded intro videos are stored # This would need to be implemented based on how uploaded intro videos are stored
# For now, we'll skip this and go to priority 3 # For now, we'll skip this and go to priority 3
logger.debug("DEBUG: Skipping uploaded intro video check (not implemented)")
# Priority 3: Fallback to INTRO.mp4 in assets directory # Priority 3: Fallback to INTRO.mp4 in assets directory
assets_dir = Path(__file__).parent.parent / "assets" assets_dir = Path(__file__).parent.parent.parent / "assets"
assets_intro = assets_dir / "INTRO.mp4" assets_intro = assets_dir / "INTRO.mp4"
logger.debug(f"DEBUG: Checking fallback INTRO.mp4 in assets: {assets_intro}")
logger.debug(f"DEBUG: Assets directory exists: {assets_dir.exists()}")
logger.debug(f"DEBUG: Assets directory contents: {list(assets_dir.iterdir()) if assets_dir.exists() else 'N/A'}")
if assets_intro.exists(): if assets_intro.exists():
logger.info(f"Using fallback INTRO.mp4 from assets: {assets_intro}") logger.info(f"Using fallback INTRO.mp4 from assets: {assets_intro}")
return assets_intro return assets_intro
...@@ -3505,6 +3543,7 @@ class QtVideoPlayer(QObject): ...@@ -3505,6 +3543,7 @@ class QtVideoPlayer(QObject):
"""Handle GAME_STATUS messages to determine when to play intro""" """Handle GAME_STATUS messages to determine when to play intro"""
try: try:
status = message.data.get("status") status = message.data.get("status")
game_active = message.data.get("game_active", False)
logger.info(f"QtPlayer: ===== RECEIVED GAME_STATUS: {status} =====") logger.info(f"QtPlayer: ===== RECEIVED GAME_STATUS: {status} =====")
logger.info(f"QtPlayer: Message sender: {message.sender}") logger.info(f"QtPlayer: Message sender: {message.sender}")
logger.info(f"QtPlayer: Message recipient: {message.recipient}") logger.info(f"QtPlayer: Message recipient: {message.recipient}")
...@@ -3518,7 +3557,13 @@ class QtVideoPlayer(QObject): ...@@ -3518,7 +3557,13 @@ class QtVideoPlayer(QObject):
elif status == "started": elif status == "started":
logger.info("QtPlayer: Game is active, intro will be handled by START_INTRO message") logger.info("QtPlayer: Game is active, intro will be handled by START_INTRO message")
elif status == "already_active": elif status == "already_active":
# Check if game is actually active or just matches exist
if game_active:
logger.info("QtPlayer: Game already active, no intro needed") logger.info("QtPlayer: Game already active, no intro needed")
else:
logger.info("QtPlayer: Matches exist but no active game, checking if we should play intro")
logger.debug("QtPlayer: Calling _check_and_play_intro() for already_active but inactive game")
self._check_and_play_intro()
else: else:
logger.debug(f"QtPlayer: Unhandled game status: {status}") logger.debug(f"QtPlayer: Unhandled game status: {status}")
...@@ -3681,3 +3726,62 @@ class QtVideoPlayer(QObject): ...@@ -3681,3 +3726,62 @@ class QtVideoPlayer(QObject):
import traceback import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}") logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
return None return None
def _find_intro_video_file_for_waiting_safe(self) -> Optional[Path]:
"""Safe version of intro video file finder that handles PyInstaller bundle issues"""
try:
logger.info("QtPlayer: ===== SAFE SEARCH FOR INTRO VIDEO FILE =====")
# Check if we're running from a PyInstaller bundle
import sys
if getattr(sys, 'frozen', False):
# Running in a PyInstaller bundle - use sys._MEIPASS
logger.info("QtPlayer: Running from PyInstaller bundle")
try:
bundle_dir = Path(sys._MEIPASS)
assets_intro = bundle_dir / "assets" / "INTRO.mp4"
logger.info(f"QtPlayer: Bundle directory: {bundle_dir}")
logger.info(f"QtPlayer: Looking for INTRO.mp4 at: {assets_intro}")
if assets_intro.exists():
logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 in bundle: {assets_intro} =====")
return assets_intro
else:
logger.warning(f"QtPlayer: INTRO.mp4 not found in bundle at: {assets_intro}")
# Try alternative locations in bundle
alt_locations = [
bundle_dir / "INTRO.mp4",
bundle_dir / "mbetterclient" / "assets" / "INTRO.mp4",
bundle_dir / "assets" / "INTRO.mp4"
]
for alt_path in alt_locations:
if alt_path.exists():
logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 at alternative location: {alt_path} =====")
return alt_path
else:
logger.debug(f"QtPlayer: Not found at: {alt_path}")
except Exception as bundle_error:
logger.error(f"QtPlayer: Error accessing PyInstaller bundle: {bundle_error}")
# Fallback to source directory
logger.info("QtPlayer: Falling back to source directory")
assets_intro = Path(__file__).parent.parent.parent / "assets" / "INTRO.mp4"
if assets_intro.exists():
logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 in source fallback: {assets_intro} =====")
return assets_intro
else:
# Running from source
logger.info("QtPlayer: Running from source")
assets_intro = Path(__file__).parent.parent.parent / "assets" / "INTRO.mp4"
if assets_intro.exists():
logger.info(f"QtPlayer: ===== FOUND INTRO.mp4 in source: {assets_intro} =====")
return assets_intro
logger.warning("QtPlayer: ===== NO INTRO VIDEO FOUND IN ANY LOCATION =====")
return None
except Exception as e:
logger.error(f"QtPlayer: Failed to find intro video safely: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
return None
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment