Update

parent 4ae8c528
No preview for this file type
......@@ -88,13 +88,15 @@ def collect_data_files() -> List[tuple]:
project_root = get_project_root()
data_files = []
# Include assets directory
# Include assets directory - ensure it's at the root level in the bundle
assets_dir = project_root / 'assets'
if assets_dir.exists():
for file_path in assets_dir.rglob('*'):
if file_path.is_file():
relative_path = file_path.relative_to(project_root)
data_files.append((str(file_path), str(relative_path.parent)))
# For assets, we want them at the root level, not nested
# So instead of relative_path.parent, we use just 'assets'
data_files.append((str(file_path), 'assets'))
print(f" 📁 Including asset file: {file_path.name} ({file_path.stat().st_size} bytes) -> assets/")
# Include web dashboard templates and static files
web_templates = project_root / 'mbetterclient' / 'web_dashboard' / 'templates'
......@@ -153,8 +155,37 @@ def collect_data_files() -> List[tuple]:
data_files.append((str(file_path), str(relative_path.parent)))
print(f" 📁 Including asset directory: {asset_dir}")
# Removed qt.conf creation for self-contained binary
print(" 📦 Building self-contained binary - no external Qt configuration needed")
# Include Qt WebEngine data files and resources
webengine_data_dirs = [
'/usr/share/qt6/resources',
'/usr/share/qt6/translations/qtwebengine_locales',
'/usr/lib/x86_64-linux-gnu/qt6/libexec',
]
for data_dir in webengine_data_dirs:
if os.path.exists(data_dir):
for root, dirs, files in os.walk(data_dir):
for file in files:
full_path = os.path.join(root, file)
rel_path = os.path.relpath(root, '/usr/share/qt6') if 'share' in data_dir else os.path.relpath(root, '/usr/lib/x86_64-linux-gnu/qt6')
data_files.append((full_path, rel_path))
print(f" 📁 Including Qt WebEngine data: {data_dir}")
# Create qt.conf to ensure Qt uses bundled libraries
qt_conf_content = """[Paths]
Prefix = .
Libraries = .
Plugins = plugins
"""
qt_conf_path = project_root / 'qt.conf'
with open(qt_conf_path, 'w') as f:
f.write(qt_conf_content)
# Include qt.conf in the build
data_files.append((str(qt_conf_path), '.'))
print(" 📦 Created qt.conf for self-contained Qt configuration")
print(" 📦 Building self-contained binary with all Qt libraries included")
return data_files
......@@ -202,10 +233,7 @@ def collect_hidden_imports() -> List[str]:
'barcode',
'barcode.codex',
'barcode.ean',
'barcode.isbn',
'barcode.code39',
'barcode.code128',
'barcode.pzn',
'barcode.isxn',
]
# Conditionally add ffmpeg module if available
......@@ -224,6 +252,14 @@ def collect_hidden_imports() -> List[str]:
except ImportError:
print(" ⚠️ PyQt6.QtDBus not available, skipping D-Bus imports")
# Add barcode submodules conditionally
try:
import barcode.isbn
hidden_imports.extend(['barcode.isbn', 'barcode.code39', 'barcode.code128', 'barcode.pzn'])
print(" 📦 Added barcode submodules to hidden imports")
except ImportError:
print(" ⚠️ Some barcode submodules not available, skipping")
# Conditionally add D-Bus modules if available
try:
import dbus
......@@ -304,18 +340,50 @@ def collect_binaries() -> List[tuple]:
('/usr/lib/x86_64-linux-gnu/qt6/plugins/platforms/libqoffscreen.so', 'platforms/'),
]
# Minimal X11 libraries - only include essential ones to avoid version conflicts
# Most X11 libraries should be provided by the system to prevent segfaults
# Essential Qt6 libraries that need to be bundled - use venv libraries instead of system ones
qt6_libraries = [
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Core.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Gui.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Widgets.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Multimedia.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6MultimediaWidgets.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6WebEngineCore.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6WebEngineWidgets.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6WebChannel.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6Network.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6DBus.so.6', '.'),
('venv/lib/python3.13/site-packages/PyQt6/Qt6/lib/libQt6OpenGL.so.6', '.'),
]
# Minimal X11 libraries - include more to ensure compatibility
essential_x11_libraries = [
('/lib/x86_64-linux-gnu/libxcb.so.1', '.'), # Core xcb library
('/lib/x86_64-linux-gnu/libX11.so.6', '.'), # Core X11 library
('/lib/x86_64-linux-gnu/libX11-xcb.so.1', '.'), # X11-xcb bridge
('/lib/x86_64-linux-gnu/libxcb-shm.so.0', '.'), # Shared memory
('/lib/x86_64-linux-gnu/libxcb-render.so.0', '.'), # Render extension
('/lib/x86_64-linux-gnu/libxcb-image.so.0', '.'), # Image utilities
('/lib/x86_64-linux-gnu/libxcb-keysyms.so.1', '.'), # Key symbols
('/lib/x86_64-linux-gnu/libxcb-randr.so.0', '.'), # RandR extension
('/lib/x86_64-linux-gnu/libxcb-xfixes.so.0', '.'), # XFixes extension
]
# GStreamer libraries for multimedia support
gstreamer_libraries = [
('/usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstvideo-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstaudio-1.0.so.0', '.'),
('/usr/lib/x86_64-linux-gnu/libgstpbutils-1.0.so.0', '.'),
]
binaries.extend(qt_platform_plugins)
binaries.extend(qt6_libraries)
binaries.extend(essential_x11_libraries)
binaries.extend(gstreamer_libraries)
print(" 📦 Including only essential X11 libraries to prevent segfaults")
print(" 💡 System will provide most X11/GL libraries to avoid version conflicts")
print(" 📦 Including Qt6, X11, and GStreamer libraries for self-contained binary")
print(" 💡 This ensures the binary works on systems without Qt6/GStreamer installed")
return binaries
......
......@@ -42,6 +42,7 @@ class GamesThread(ThreadedComponent):
self.message_bus.subscribe(self.name, MessageType.MATCH_START, self._handle_match_start)
self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_MATCH_DONE, self._handle_play_video_match_done)
self.message_bus.subscribe(self.name, MessageType.MATCH_DONE, self._handle_match_done)
self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status_request)
self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status)
# Send ready status
......@@ -265,6 +266,34 @@ class GamesThread(ThreadedComponent):
except Exception as fallback_e:
logger.error(f"Fallback betting calculation also failed: {fallback_e}")
def _handle_game_status_request(self, message: Message):
"""Handle GAME_STATUS requests from Qt player"""
try:
logger.info(f"Received GAME_STATUS request from {message.sender}")
# Determine current game status
game_status = self._determine_game_status()
# Send GAME_STATUS response back to the requester
response = Message(
type=MessageType.GAME_STATUS,
sender=self.name,
recipient=message.sender,
data={
"status": game_status,
"fixture_id": self.current_fixture_id,
"game_active": self.game_active,
"timestamp": time.time()
},
correlation_id=message.correlation_id
)
# Broadcast the response instead of sending to specific recipient
self.message_bus.publish(response, broadcast=True)
logger.info(f"Broadcast game status response: {game_status}")
except Exception as e:
logger.error(f"Failed to handle game status request: {e}")
def _process_message(self, message: Message):
"""Process incoming messages"""
try:
......@@ -277,12 +306,16 @@ class GamesThread(ThreadedComponent):
self._handle_schedule_games(message)
elif message.type == MessageType.SYSTEM_SHUTDOWN:
self._handle_shutdown_message(message)
elif message.type == MessageType.SYSTEM_STATUS:
self._handle_system_status(message)
elif message.type == MessageType.GAME_UPDATE:
self._handle_game_update(message)
elif message.type == MessageType.PLAY_VIDEO_MATCH_DONE:
self._handle_play_video_match_done(message)
elif message.type == MessageType.MATCH_DONE:
self._handle_match_done(message)
elif message.type == MessageType.GAME_STATUS:
self._handle_game_status_request(message)
except Exception as e:
logger.error(f"Failed to process message: {e}")
......@@ -1315,9 +1348,12 @@ class GamesThread(ThreadedComponent):
logger.error(f"Failed to handle MATCH_DONE message: {e}")
def _handle_system_status(self, message: Message):
"""Handle SYSTEM_STATUS messages, particularly fixture update completion"""
"""Handle SYSTEM_STATUS messages, particularly fixture update completion and status requests"""
try:
logger.debug(f"GamesThread handling SYSTEM_STATUS message from {message.sender}: {message.data}")
status = message.data.get("status")
details = message.data.get("details", {})
if status == "fixture_update_completed":
synchronized_matches = message.data.get("synchronized_matches", 0)
downloaded_zips = message.data.get("downloaded_zips", 0)
......@@ -1338,8 +1374,43 @@ class GamesThread(ThreadedComponent):
)
self.message_bus.publish(start_game_message)
elif status == "status_request":
# Handle status requests from Qt player
request_type = details.get("request_type")
logger.debug(f"Status request type: {request_type}, details: {details}")
if request_type == "game_status":
logger.info(f"Received game status request from {message.sender}")
# Determine current game status
game_status = self._determine_game_status()
logger.debug(f"Determined game status: {game_status}")
# Send GAME_STATUS response back to the requester (broadcast to ensure delivery)
response = Message(
type=MessageType.GAME_STATUS,
sender=self.name,
recipient=message.sender,
data={
"status": game_status,
"fixture_id": self.current_fixture_id,
"game_active": self.game_active,
"timestamp": time.time()
},
correlation_id=message.correlation_id
)
logger.debug(f"About to publish GAME_STATUS response: {response.data}")
# Broadcast the response to ensure it reaches the Qt player
self.message_bus.publish(response, broadcast=True)
logger.info(f"Sent game status response to {message.sender}: {game_status}")
else:
logger.debug(f"Ignoring status_request with unknown request_type: {request_type}")
else:
logger.debug(f"Ignoring SYSTEM_STATUS message with unknown status: {status}")
except Exception as e:
logger.error(f"Failed to handle system status message: {e}")
import traceback
logger.error(f"Full traceback: {traceback.format_exc()}")
def _set_match_status(self, match_id: int, status: str):
"""Set match status in database"""
......@@ -1869,6 +1940,53 @@ class GamesThread(ThreadedComponent):
session.rollback()
return None
def _determine_game_status(self) -> str:
"""Determine the current game status for status requests"""
try:
# If a game is currently active, return "started"
if self.game_active and self.current_fixture_id:
return "started"
# Check if there are any active fixtures (matches in non-terminal states)
session = self.db_manager.get_session()
try:
# Get today's date
today = datetime.now().date()
# Check for active matches today
active_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.notin_(['done', 'cancelled', 'failed', 'paused']),
MatchModel.active_status == True
).all()
if active_matches:
return "already_active"
# Check if all today's fixtures are in terminal states
if self._has_today_fixtures_all_terminal():
return "completed_no_old_matches"
# Check if there are any fixtures at all (even if not today)
any_fixtures = session.query(MatchModel).filter(
MatchModel.active_status == True
).count()
if any_fixtures > 0:
return "ready" # Fixtures exist but no active game
# No fixtures at all
return "shutdown"
finally:
session.close()
except Exception as e:
logger.error(f"Failed to determine game status: {e}")
return "ready" # Default fallback
def _cleanup(self):
"""Perform cleanup operations"""
try:
......
......@@ -99,6 +99,8 @@ class ThreadedComponent(ABC):
except Exception as e:
logger.error(f"Failed to stop component {self.name}: {e}")
import traceback
logger.error(f"Full traceback: {traceback.format_exc()}")
return False
def _thread_wrapper(self):
......@@ -237,11 +239,15 @@ class ThreadManager:
with self._lock:
for name, component in self.components.items():
if not component.stop(stop_timeout):
logger.error(f"Failed to stop component {name}")
try:
if not component.stop(stop_timeout):
logger.error(f"Failed to stop component {name}")
success = False
else:
logger.info(f"Component {name} stopped")
except Exception as e:
logger.error(f"Exception stopping component {name}: {e}")
success = False
else:
logger.info(f"Component {name} stopped")
if success:
logger.info("All components stopped successfully")
......
......@@ -1334,6 +1334,13 @@ class PlayerWindow(QMainWindow):
logger.info(f"Loop data: {loop_data}")
logger.info(f"Media player state before play: {self.media_player.playbackState()}")
logger.info(f"Media player error state: {self.media_player.error()}")
logger.info(f"Media player has video output: {self.media_player.videoOutput() is not None}")
logger.info(f"Video widget visible: {self.video_widget.get_video_widget().isVisible()}")
logger.info(f"Video widget size: {self.video_widget.get_video_widget().size()}")
logger.info(f"Window visible: {self.isVisible()}")
logger.info(f"Window size: {self.size()}")
logger.info(f"Overlay window visible: {hasattr(self, 'overlay_window') and self.overlay_window.isVisible()}")
logger.info(f"Overlay window size: {hasattr(self, 'overlay_window') and self.overlay_window.size()}")
# Process loop control parameters
if loop_data:
......@@ -1402,13 +1409,18 @@ class PlayerWindow(QMainWindow):
url = QUrl.fromLocalFile(str(absolute_path))
logger.info(f"Created QUrl: {url.toString()}")
logger.info(f"QUrl is valid: {url.isValid()}")
logger.info(f"QUrl scheme: {url.scheme()}")
logger.info(f"QUrl path: {url.path()}")
# Store current file path for loop functionality
self.current_file_path = str(absolute_path)
logger.info(f"Media player current state: {self.media_player.playbackState()}")
logger.info(f"Media player current source before set: {self.media_player.source()}")
self.media_player.setSource(url)
logger.info(f"Media player source set to: {url.toString()}")
logger.info(f"Media player source after set: {self.media_player.source()}")
logger.info(f"Media player error after setSource: {self.media_player.error()}")
# CRITICAL FIX: Protect video rendering context during template operations
logger.debug(f"GREEN SCREEN FIX: Protecting video rendering context during template load")
......@@ -1457,17 +1469,25 @@ class PlayerWindow(QMainWindow):
self._start_template_rotation()
if self.settings.auto_play:
logger.info("Auto-play enabled, calling media_player.play()")
self.media_player.play()
logger.info(f"Media player state after play(): {self.media_player.playbackState()}")
logger.info(f"Media player error after play(): {self.media_player.error()}")
else:
logger.info("Auto-play disabled, not calling play()")
# Ensure proper window focus for video playback
app = QApplication.instance()
if app:
logger.info("Applying window focus for video playback")
self.show()
self.raise_()
self.activateWindow()
app.processEvents()
app.setActiveWindow(self)
logger.debug(f"Window focus applied for video playback - activeWindow: {app.activeWindow()}")
logger.debug(f"Window visible after focus: {self.isVisible()}")
logger.debug(f"Video widget visible after focus: {self.video_widget.get_video_widget().isVisible()}")
else:
logger.warning("No QApplication instance found for window focus")
......@@ -1542,7 +1562,12 @@ class PlayerWindow(QMainWindow):
"""Handle playback state changes (thread-safe)"""
is_playing = state == QMediaPlayer.PlaybackState.PlayingState
# Controls removed - no button updates needed
logger.debug(f"Playback state changed: {'playing' if is_playing else 'paused'}")
logger.info(f"Playback state changed: {'playing' if is_playing else 'paused'}")
logger.info(f"Media player state: {state}")
logger.info(f"Media player error: {self.media_player.error()}")
logger.info(f"Media player source: {self.media_player.source()}")
logger.info(f"Video widget visible: {self.video_widget.get_video_widget().isVisible()}")
logger.info(f"Window visible: {self.isVisible()}")
def on_position_changed(self, position):
"""Handle position changes (thread-safe)"""
......@@ -1586,6 +1611,11 @@ class PlayerWindow(QMainWindow):
def on_media_status_changed(self, status):
"""Handle media status changes and loop control"""
logger.info(f"LOOP DEBUG: Media status changed to: {status} ({status.name if hasattr(status, 'name') else 'unknown'})")
logger.info(f"Media player source: {self.media_player.source()}")
logger.info(f"Media player error: {self.media_player.error()}")
logger.info(f"Video widget visible: {self.video_widget.get_video_widget().isVisible()}")
logger.info(f"Window visible: {self.isVisible()}")
logger.debug(f"LOOP DEBUG: Media status changed to: {status} ({status.name if hasattr(status, 'name') else 'unknown'})")
if status == QMediaPlayer.MediaStatus.LoadedMedia:
......@@ -2080,6 +2110,7 @@ class QtVideoPlayer(QObject):
self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_MATCH, self._handle_play_video_match)
self.message_bus.subscribe(self.name, MessageType.PLAY_VIDEO_RESULT, self._handle_play_video_result)
self.message_bus.subscribe(self.name, MessageType.GAME_STATUS, self._handle_game_status)
self.message_bus.subscribe(self.name, MessageType.SYSTEM_STATUS, self._handle_system_status)
logger.info("QtPlayer subscriptions completed successfully")
# Delay loading default overlay to allow JavaScript initialization
......@@ -3448,55 +3479,119 @@ class QtVideoPlayer(QObject):
def _request_initial_game_status(self):
"""Request initial game status to determine if we should play intro"""
try:
logger.info("Requesting initial game status to check if intro should be played")
logger.info("QtPlayer: ===== REQUESTING INITIAL GAME STATUS =====")
logger.debug("QtPlayer: About to publish status_request message")
# Send GAME_STATUS message instead of SYSTEM_STATUS to match games_thread subscription
status_request = MessageBuilder.system_status(
sender=self.name,
status="status_request",
details={"request_type": "game_status"}
)
logger.info(f"QtPlayer: Created status_request message: {status_request}")
logger.info(f"QtPlayer: Message sender: {status_request.sender}")
logger.info(f"QtPlayer: Message type: {status_request.type}")
logger.info(f"QtPlayer: Message data: {status_request.data}")
self.message_bus.publish(status_request, broadcast=True)
logger.info("QtPlayer: ===== SUCCESSFULLY PUBLISHED STATUS_REQUEST MESSAGE =====")
except Exception as e:
logger.error(f"Failed to request initial game status: {e}")
logger.error(f"QtPlayer: Failed to request initial game status: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
# If message creation fails, try to play intro anyway
logger.info("QtPlayer: Message creation failed, trying to play intro directly")
self._check_and_play_intro()
def _handle_game_status(self, message: Message):
"""Handle GAME_STATUS messages to determine when to play intro"""
try:
status = message.data.get("status")
logger.info(f"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 recipient: {message.recipient}")
logger.info(f"QtPlayer: Full message data: {message.data}")
# Check if this indicates no active game
if status in ["ready", "shutdown", "completed_no_old_matches"]:
logger.info("No active game detected, checking if we should play intro")
logger.info("QtPlayer: No active game detected, checking if we should play intro")
logger.debug("QtPlayer: Calling _check_and_play_intro()")
self._check_and_play_intro()
elif status == "started":
logger.info("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":
logger.info("Game already active, no intro needed")
logger.info("QtPlayer: Game already active, no intro needed")
else:
logger.debug(f"QtPlayer: Unhandled game status: {status}")
except Exception as e:
logger.error(f"QtPlayer: Failed to handle game status: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
# If handling fails, try to play intro anyway
logger.info("QtPlayer: Game status handling failed, trying to play intro directly")
self._check_and_play_intro()
def _handle_system_status(self, message: Message):
"""Handle SYSTEM_STATUS messages that might contain game status info"""
try:
status = message.data.get("status")
logger.info(f"QtPlayer: ===== RECEIVED SYSTEM_STATUS: {status} =====")
logger.info(f"QtPlayer: Message sender: {message.sender}")
logger.info(f"QtPlayer: Message data: {message.data}")
# If this is a game status response, handle it like GAME_STATUS
if status in ["ready", "shutdown", "completed_no_old_matches", "started", "already_active"]:
logger.info("QtPlayer: SYSTEM_STATUS contains game status, treating as GAME_STATUS")
self._handle_game_status(message)
except Exception as e:
logger.error(f"Failed to handle game status: {e}")
logger.error(f"QtPlayer: Failed to handle system status: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
# If handling fails, try to play intro anyway
logger.info("QtPlayer: System status handling failed, trying to play intro directly")
self._check_and_play_intro()
def _check_and_play_intro(self):
"""Check if we should play the intro video when no game is active"""
try:
logger.info("QtPlayer: ===== CHECKING IF INTRO SHOULD PLAY =====")
# Only play intro if we're not already playing something
if self.window and self.window.media_player.playbackState() == QMediaPlayer.PlaybackState.StoppedState:
logger.info("Player is stopped, playing waiting intro")
playback_state = self.window.media_player.playbackState()
currently_playing = (playback_state == QMediaPlayer.PlaybackState.PlayingState)
logger.info(f"QtPlayer: Checking if intro should play - currently playing: {currently_playing}")
logger.info(f"QtPlayer: Media player state: {playback_state}")
logger.info(f"QtPlayer: Media player state name: {playback_state.name if hasattr(playback_state, 'name') else 'unknown'}")
logger.info(f"QtPlayer: Media player source: {self.window.media_player.source()}")
logger.info(f"QtPlayer: Media player duration: {self.window.media_player.duration()}")
logger.info(f"QtPlayer: Media player position: {self.window.media_player.position()}")
if self.window and playback_state == QMediaPlayer.PlaybackState.StoppedState:
logger.info("QtPlayer: Player is stopped, calling _play_waiting_intro()")
logger.debug("QtPlayer: About to call _play_waiting_intro()")
self._play_waiting_intro()
else:
logger.info("Player is already playing, skipping intro")
logger.info("QtPlayer: Player is NOT stopped, skipping intro")
logger.info(f"QtPlayer: Skipping intro due to playback state: {playback_state} ({playback_state.name if hasattr(playback_state, 'name') else 'unknown'})")
except Exception as e:
logger.error(f"Failed to check and play intro: {e}")
logger.error(f"QtPlayer: Failed to check and play intro: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
def _play_waiting_intro(self):
"""Play the INTRO video on repeat with waiting overlay when no game is active"""
try:
logger.info("Playing waiting intro video")
logger.info("QtPlayer: ===== STARTING _play_waiting_intro() =====")
logger.debug("QtPlayer: About to find intro video file")
# Find the INTRO video file
intro_path = self._find_intro_video_file_for_waiting()
logger.info(f"QtPlayer: Intro path result: {intro_path}")
logger.info(f"QtPlayer: Intro path exists: {intro_path.exists() if intro_path else 'N/A'}")
logger.info(f"QtPlayer: Intro path is file: {intro_path.is_file() if intro_path else 'N/A'}")
if intro_path:
logger.info(f"QtPlayer: ===== PLAYING INTRO VIDEO: {intro_path} =====")
logger.debug("QtPlayer: Setting up loop data for infinite repeat")
# Set up loop control for infinite repeat
loop_data = {
'infinite_loop': True,
......@@ -3509,37 +3604,80 @@ class QtVideoPlayer(QObject):
"message": "Waiting for game to start...",
"icon": "⏳"
}
logger.info(f"QtPlayer: Overlay data prepared: {overlay_data}")
# Play the intro video with loop and overlay
if self.window:
logger.info("QtPlayer: Window available, calling play_video")
logger.info(f"QtPlayer: About to call window.play_video with file: {str(intro_path)}")
self.window.play_video(
str(intro_path),
template_data=overlay_data,
template_name="default.html", # Use default template
loop_data=loop_data
)
logger.info("Waiting intro video started with overlay")
logger.info("QtPlayer: ===== WAITING INTRO VIDEO STARTED SUCCESSFULLY =====")
logger.debug("QtPlayer: play_video call completed successfully")
else:
logger.error("No window available for waiting intro playback")
logger.error("QtPlayer: No window available for waiting intro playback")
else:
logger.warning("No INTRO video found for waiting state")
logger.warning("QtPlayer: No INTRO video found for waiting state")
logger.warning("QtPlayer: Intro path was None, cannot play waiting intro")
except Exception as e:
logger.error(f"Failed to play waiting intro: {e}")
logger.error(f"QtPlayer: Failed to play waiting intro: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
def _find_intro_video_file_for_waiting(self) -> Optional[Path]:
"""Find the INTRO video file for waiting state"""
try:
# Priority 1: Check for INTRO.mp4 in assets directory
assets_dir = Path(__file__).parent.parent / "assets"
assets_intro = assets_dir / "INTRO.mp4"
logger.info("QtPlayer: ===== SEARCHING 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
logger.info("QtPlayer: Running from PyInstaller bundle")
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}")
else:
# Running from source
logger.info("QtPlayer: Running from source")
assets_intro = Path(__file__).parent.parent / "assets" / "INTRO.mp4"
logger.info(f"QtPlayer: INTRO.mp4 path: {assets_intro}")
logger.info(f"QtPlayer: INTRO.mp4 exists: {assets_intro.exists()}")
if assets_intro.exists():
logger.info(f"Using INTRO.mp4 from assets for waiting: {assets_intro}")
logger.info(f"QtPlayer: ===== FOUND INTRO.mp4: {assets_intro} =====")
logger.info(f"QtPlayer: File size: {assets_intro.stat().st_size} bytes")
logger.info(f"QtPlayer: File permissions: {oct(assets_intro.stat().st_mode)}")
logger.info(f"QtPlayer: File is readable: {assets_intro.stat().st_mode & 0o400 != 0}")
return assets_intro
else:
logger.warning(f"QtPlayer: INTRO.mp4 not found at: {assets_intro}")
# Debug: Check what files are available in the bundle directory
if getattr(sys, 'frozen', False):
bundle_dir = Path(sys._MEIPASS)
logger.warning(f"QtPlayer: Bundle directory contents: {list(bundle_dir.iterdir()) if bundle_dir.exists() else 'bundle dir not found'}")
assets_dir = bundle_dir / "assets"
if assets_dir.exists():
logger.warning(f"QtPlayer: Assets directory contents: {list(assets_dir.iterdir())}")
else:
logger.warning("QtPlayer: Assets directory not found in bundle")
else:
source_assets = Path(__file__).parent.parent / "assets"
logger.warning(f"QtPlayer: Source assets directory contents: {list(source_assets.iterdir()) if source_assets.exists() else 'source assets dir not found'}")
logger.warning("No INTRO video found for waiting state")
logger.warning("QtPlayer: ===== NO INTRO VIDEO FOUND =====")
return None
except Exception as e:
logger.error(f"Failed to find intro video for waiting: {e}")
logger.error(f"QtPlayer: Failed to find intro video for waiting: {e}")
import traceback
logger.error(f"QtPlayer: Full traceback: {traceback.format_exc()}")
return None
......@@ -222,9 +222,9 @@
<script>
// Global variables for overlay data handling
let overlayData = {};
let currentTitle = 'Announcement';
let currentMessage = 'This is a custom message from the system.';
let currentIcon = '📢';
let currentTitle = 'Mbetter system:';
let currentMessage = 'Waiting for game to start...';
let currentIcon = '🥊';
// Function to update overlay data (called by Qt WebChannel)
function updateOverlayData(data) {
......
......@@ -3702,8 +3702,8 @@ def create_result_options_migration():
from ..database.migrations import DatabaseMigration
class Migration_023(DatabaseMigration):
version = "023"
description = "Add result_options table for extraction system results area"
def __init__(self):
super().__init__("023", "Add result_options table for extraction system results area")
def up(self, db_manager):
"""Apply migration"""
......
......@@ -713,6 +713,17 @@ function updateAvailableMatchesDisplay(data, container) {
// Add event listeners for amount inputs only
container.querySelectorAll('.amount-input').forEach(input => {
input.addEventListener('input', function() {
// Clear other outcomes for this match when entering an amount
const matchId = this.getAttribute('data-match-id');
const currentOutcome = this.getAttribute('data-outcome');
// Clear all other amount inputs for this match
container.querySelectorAll(`.amount-input[data-match-id="${matchId}"]`).forEach(otherInput => {
if (otherInput !== this) {
otherInput.value = '';
}
});
updateBetSummary();
});
});
......
[Paths]
Prefix = .
Libraries = .
Plugins = plugins
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