Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterc
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterc
Commits
63ca5331
Commit
63ca5331
authored
Nov 19, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Start intro managed
parent
e9e2f6c9
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
191 additions
and
24 deletions
+191
-24
games_thread.py
mbetterclient/core/games_thread.py
+67
-4
player.py
mbetterclient/qt_player/player.py
+124
-20
No files found.
mbetterclient/core/games_thread.py
View file @
63ca5331
...
...
@@ -27,11 +27,52 @@ class GamesThread(ThreadedComponent):
self
.
_shutdown_event
=
threading
.
Event
()
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
:
"""Initialize the games thread"""
try
:
logger
.
info
(
"Initializing GamesThread..."
)
# Clean up any stale 'ingame' matches from previous crashed sessions
self
.
_cleanup_stale_ingame_matches
()
# Register with message bus first
self
.
message_queue
=
self
.
message_bus
.
register_component
(
self
.
name
)
...
...
@@ -274,6 +315,25 @@ class GamesThread(ThreadedComponent):
# Determine current 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
response
=
Message
(
type
=
MessageType
.
GAME_STATUS
,
...
...
@@ -548,9 +608,9 @@ class GamesThread(ThreadedComponent):
timer_running
=
self
.
_is_timer_running_for_fixture
(
fixture_id
)
if
not
timer_running
:
# Timer not running, change status to
failed
logger
.
info
(
f
"Timer not running for fixture {fixture_id}, changing ingame matches to
failed
"
)
self
.
_change_fixture_matches_status
(
fixture_id
,
'ingame'
,
'
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
pending and setting active_status to False
"
)
self
.
_change_fixture_matches_status
(
fixture_id
,
'ingame'
,
'
pending'
,
active_status_to_set
=
False
)
# Check if this was the only non-terminal fixture
if
self
.
_is_only_non_terminal_fixture
(
fixture_id
):
...
...
@@ -593,7 +653,7 @@ class GamesThread(ThreadedComponent):
# In a real implementation, you'd check the match_timer component status
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"""
try
:
session
=
self
.
db_manager
.
get_session
()
...
...
@@ -607,6 +667,9 @@ class GamesThread(ThreadedComponent):
for
match
in
matches
:
logger
.
info
(
f
"Changing match {match.match_number} status from {from_status} to {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
()
...
...
mbetterclient/qt_player/player.py
View file @
63ca5331
...
...
@@ -342,27 +342,27 @@ class OverlayWebView(QWebEngineView):
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Starting template load - {template_name}"
)
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Current page URL before load: {self.url().toString()}"
)
logger
.
debug
(
f
"GREEN SCREEN DEBUG: WebEngine view visible: {self.isVisible()}"
)
# CRITICAL FIX: Store visibility state before template load
was_visible
=
self
.
isVisible
()
# If no template name provided, use default
if
not
template_name
:
template_name
=
"default.html"
# Ensure .html extension
if
not
template_name
.
endswith
(
'.html'
):
template_name
+=
'.html'
# First try uploaded templates directory (user uploads take priority)
template_path
=
self
.
uploaded_templates_dir
/
template_name
template_source
=
"uploaded"
# If not found in uploaded, try built-in templates
if
not
template_path
.
exists
():
template_path
=
self
.
builtin_templates_dir
/
template_name
template_source
=
"builtin"
# If still not found, fallback to default.html in built-in templates
if
not
template_path
.
exists
():
default_template_path
=
self
.
builtin_templates_dir
/
"default.html"
...
...
@@ -376,7 +376,7 @@ class OverlayWebView(QWebEngineView):
# Load fallback minimal overlay
self
.
_load_fallback_overlay
()
return
if
template_path
and
template_path
.
exists
():
if
self
.
debug_overlay
:
logger
.
debug
(
f
"GREEN SCREEN DEBUG: About to load template file: {template_path}"
)
...
...
@@ -387,6 +387,24 @@ class OverlayWebView(QWebEngineView):
if
page
:
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
.
current_template
=
template_name
...
...
@@ -408,7 +426,7 @@ class OverlayWebView(QWebEngineView):
logger
.
error
(
f
"No template found: {template_name}"
)
# Load fallback minimal overlay
self
.
_load_fallback_overlay
()
except
Exception
as
e
:
if
self
.
debug_overlay
:
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Template load failed: {e}"
)
...
...
@@ -1433,16 +1451,28 @@ class PlayerWindow(QMainWindow):
if
video_widget
:
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
if
hasattr
(
self
,
'window_overlay'
)
and
isinstance
(
self
.
window_overlay
,
OverlayWebView
):
if
template_name
:
logger
.
debug
(
f
"GREEN SCREEN FIX: Loading template while protecting video context: {template_name}"
)
self
.
window_overlay
.
load_template
(
template_name
)
logger
.
info
(
f
"Loaded template '{template_name}' for video playback"
)
else
:
logger
.
debug
(
f
"GREEN SCREEN FIX: Reloading template while protecting video context"
)
self
.
window_overlay
.
reload_current_template
()
logger
.
info
(
"Reloaded current overlay template for video playback"
)
# 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
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
)
logger
.
info
(
f
"Loaded template '{template_name}' for video playback"
)
else
:
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
()
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
if
video_widget
:
...
...
@@ -3096,6 +3126,8 @@ class QtVideoPlayer(QObject):
def
_find_intro_video_file
(
self
,
match_id
:
int
)
->
Optional
[
Path
]:
"""Find the correct intro video file based on priority"""
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
if
match_id
:
from
..database.manager
import
DatabaseManager
...
...
@@ -3110,10 +3142,12 @@ class QtVideoPlayer(QObject):
import
tempfile
import
os
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}*"
):
if
temp_dir
.
is_dir
():
intro_file
=
temp_dir
/
"INTRO.mp4"
logger
.
debug
(
f
"DEBUG: Checking for INTRO.mp4 in match ZIP: {intro_file}"
)
if
intro_file
.
exists
():
logger
.
info
(
f
"Found INTRO.mp4 in match ZIP: {intro_file}"
)
return
intro_file
...
...
@@ -3121,10 +3155,14 @@ class QtVideoPlayer(QObject):
# Priority 2: Check for uploaded intro video
# 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
logger
.
debug
(
"DEBUG: Skipping uploaded intro video check (not implemented)"
)
# 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"
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
():
logger
.
info
(
f
"Using fallback INTRO.mp4 from assets: {assets_intro}"
)
return
assets_intro
...
...
@@ -3505,6 +3543,7 @@ class QtVideoPlayer(QObject):
"""Handle GAME_STATUS messages to determine when to play intro"""
try
:
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: Message sender: {message.sender}"
)
logger
.
info
(
f
"QtPlayer: Message recipient: {message.recipient}"
)
...
...
@@ -3518,7 +3557,13 @@ class QtVideoPlayer(QObject):
elif
status
==
"started"
:
logger
.
info
(
"QtPlayer: Game is active, intro will be handled by START_INTRO message"
)
elif
status
==
"already_active"
:
logger
.
info
(
"QtPlayer: Game already active, no intro needed"
)
# Check if game is actually active or just matches exist
if
game_active
:
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
:
logger
.
debug
(
f
"QtPlayer: Unhandled game status: {status}"
)
...
...
@@ -3681,3 +3726,62 @@ class QtVideoPlayer(QObject):
import
traceback
logger
.
error
(
f
"QtPlayer: Full traceback: {traceback.format_exc()}"
)
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment