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
0d4bd057
Commit
0d4bd057
authored
Nov 25, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
NEXT_MATCH fixed
parent
549596de
Changes
6
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
280 additions
and
172 deletions
+280
-172
games_thread.py
mbetterclient/core/games_thread.py
+4
-8
match_timer.py
mbetterclient/core/match_timer.py
+15
-7
player.py
mbetterclient/qt_player/player.py
+26
-14
match.html
mbetterclient/qt_player/templates/match.html
+62
-34
results.html
mbetterclient/qt_player/templates/results.html
+130
-102
overlay.js
mbetterclient/web_dashboard/static/overlay.js
+43
-7
No files found.
mbetterclient/core/games_thread.py
View file @
0d4bd057
...
@@ -1775,8 +1775,8 @@ class GamesThread(ThreadedComponent):
...
@@ -1775,8 +1775,8 @@ class GamesThread(ThreadedComponent):
# Send MATCH_DONE message with result
# Send MATCH_DONE message with result
self
.
_send_match_done
(
fixture_id
,
match_id
,
result
)
self
.
_send_match_done
(
fixture_id
,
match_id
,
result
)
# Send
START_INTRO message to start the next match cycle
# Send
NEXT_MATCH message to advance to next match
self
.
_
dispatch_start_intro
(
fixture
_id
)
self
.
_
send_next_match
(
fixture_id
,
match
_id
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle PLAY_VIDEO_RESULTS_DONE message: {e}"
)
logger
.
error
(
f
"Failed to handle PLAY_VIDEO_RESULTS_DONE message: {e}"
)
...
@@ -1818,12 +1818,8 @@ class GamesThread(ThreadedComponent):
...
@@ -1818,12 +1818,8 @@ class GamesThread(ThreadedComponent):
finally
:
finally
:
session
.
close
()
session
.
close
()
# Wait 2 seconds then send NEXT_MATCH
# NEXT_MATCH is now sent immediately in _handle_play_video_result_done
import
time
# to avoid the 2-second delay and ensure proper sequencing
time
.
sleep
(
2
)
# Send NEXT_MATCH message
self
.
_send_next_match
(
fixture_id
,
match_id
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle MATCH_DONE message: {e}"
)
logger
.
error
(
f
"Failed to handle MATCH_DONE message: {e}"
)
...
...
mbetterclient/core/match_timer.py
View file @
0d4bd057
...
@@ -243,16 +243,24 @@ class MatchTimerComponent(ThreadedComponent):
...
@@ -243,16 +243,24 @@ class MatchTimerComponent(ThreadedComponent):
logger
.
info
(
f
"Received NEXT_MATCH message for fixture {fixture_id}, match {match_id}"
)
logger
.
info
(
f
"Received NEXT_MATCH message for fixture {fixture_id}, match {match_id}"
)
logger
.
info
(
"Previous match completed - restarting timer for next interval"
)
logger
.
info
(
"Previous match completed - restarting timer for next interval"
)
# Find and start the next match
# Start timer first to ensure countdown is visible immediately
match_interval
=
self
.
_get_match_interval
()
self
.
_start_timer
(
match_interval
*
60
,
fixture_id
)
logger
.
info
(
f
"Timer started for {match_interval} minute interval"
)
# Then find and start the next match
match_info
=
self
.
_find_and_start_next_match
()
match_info
=
self
.
_find_and_start_next_match
()
if
match_info
:
if
match_info
:
logger
.
info
(
f
"
Start
ed next match {match_info['match_id']} in fixture {match_info['fixture_id']}"
)
logger
.
info
(
f
"
Prepar
ed next match {match_info['match_id']} in fixture {match_info['fixture_id']}"
)
# Reset timer for next interval
# Update timer with correct fixture_id if different
match_interval
=
self
.
_get_match_interval
()
if
match_info
[
'fixture_id'
]
!=
fixture_id
:
self
.
_start_timer
(
match_interval
*
60
,
match_info
[
'fixture_id'
])
with
self
.
_timer_lock
:
logger
.
info
(
f
"Timer restarted for {match_interval} minute interval"
)
self
.
current_fixture_id
=
match_info
[
'fixture_id'
]
# Send updated timer info
self
.
_send_timer_update
()
logger
.
info
(
f
"Timer updated with fixture {match_info['fixture_id']}"
)
else
:
else
:
logger
.
info
(
"No more matches to start, stopping timer"
)
logger
.
info
(
"No more matches to start, stopping timer"
)
self
.
_stop_timer
()
self
.
_stop_timer
()
...
...
mbetterclient/qt_player/player.py
View file @
0d4bd057
...
@@ -118,35 +118,41 @@ class OverlayWebChannel(QObject):
...
@@ -118,35 +118,41 @@ class OverlayWebChannel(QObject):
"""Send data update to JavaScript (thread-safe)"""
"""Send data update to JavaScript (thread-safe)"""
# Validate data before sending to prevent null emissions
# Validate data before sending to prevent null emissions
if
not
data
:
if
not
data
:
logger
.
warning
(
"send_data_update called with null/empty data, skipping"
)
logger
.
warning
(
"
RESULTS DEBUG:
send_data_update called with null/empty data, skipping"
)
return
return
# Debug original data before cleaning
# Debug original data before cleaning
logger
.
debug
(
f
"OverlayWebChannel received data: {data}, type: {type(data)}"
)
logger
.
info
(
f
"RESULTS DEBUG: OverlayWebChannel received data: {data}, type: {type(data)}"
)
logger
.
debug
(
f
"OverlayWebChannel data keys: {list(data.keys()) if isinstance(data, dict) else 'not dict'}"
)
logger
.
info
(
f
"RESULTS DEBUG: OverlayWebChannel data keys: {list(data.keys()) if isinstance(data, dict) else 'not dict'}"
)
# Check if this data contains results information
has_results_data
=
any
(
key
in
data
for
key
in
[
'outcome'
,
'result'
,
'match'
,
'match_id'
,
'fixture_id'
])
logger
.
info
(
f
"RESULTS DEBUG: Data contains results info: {has_results_data}"
)
# Clean data to remove null/undefined values before sending to JavaScript
# Clean data to remove null/undefined values before sending to JavaScript
cleaned_data
=
self
.
_clean_data
(
data
)
cleaned_data
=
self
.
_clean_data
(
data
)
logger
.
debug
(
f
"
OverlayWebChannel cleaned data: {cleaned_data}"
)
logger
.
info
(
f
"RESULTS DEBUG:
OverlayWebChannel cleaned data: {cleaned_data}"
)
if
not
cleaned_data
:
if
not
cleaned_data
:
logger
.
debug
(
"
All data properties were null/undefined, skipping JavaScript update"
)
logger
.
info
(
"RESULTS DEBUG:
All data properties were null/undefined, skipping JavaScript update"
)
return
return
# Debug what data is being sent to JavaScript
# Debug what data is being sent to JavaScript
data_keys
=
list
(
cleaned_data
.
keys
())
if
isinstance
(
cleaned_data
,
dict
)
else
[]
data_keys
=
list
(
cleaned_data
.
keys
())
if
isinstance
(
cleaned_data
,
dict
)
else
[]
logger
.
debug
(
f
"
OverlayWebChannel sending to JavaScript: {len(cleaned_data)} items with keys: {data_keys}"
)
logger
.
info
(
f
"RESULTS DEBUG:
OverlayWebChannel sending to JavaScript: {len(cleaned_data)} items with keys: {data_keys}"
)
logger
.
debug
(
f
"
Data type: {type(cleaned_data)}, Data is dict: {isinstance(cleaned_data, dict)}"
)
logger
.
info
(
f
"RESULTS DEBUG:
Data type: {type(cleaned_data)}, Data is dict: {isinstance(cleaned_data, dict)}"
)
with
QMutexLocker
(
self
.
mutex
):
with
QMutexLocker
(
self
.
mutex
):
self
.
overlay_data
.
update
(
cleaned_data
)
self
.
overlay_data
.
update
(
cleaned_data
)
logger
.
info
(
f
"RESULTS DEBUG: Updated overlay_data, now contains: {list(self.overlay_data.keys())}"
)
# Add additional validation just before emit
# Add additional validation just before emit
if
cleaned_data
and
isinstance
(
cleaned_data
,
dict
)
and
any
(
v
is
not
None
for
v
in
cleaned_data
.
values
()):
if
cleaned_data
and
isinstance
(
cleaned_data
,
dict
)
and
any
(
v
is
not
None
for
v
in
cleaned_data
.
values
()):
logger
.
debug
(
f
"
OverlayWebChannel emitting dataUpdated signal with: {cleaned_data}"
)
logger
.
info
(
f
"RESULTS DEBUG:
OverlayWebChannel emitting dataUpdated signal with: {cleaned_data}"
)
self
.
dataUpdated
.
emit
(
cleaned_data
)
self
.
dataUpdated
.
emit
(
cleaned_data
)
data_keys
=
list
(
cleaned_data
.
keys
())
if
isinstance
(
cleaned_data
,
dict
)
else
[]
data_keys
=
list
(
cleaned_data
.
keys
())
if
isinstance
(
cleaned_data
,
dict
)
else
[]
logger
.
debug
(
f
"
Signal emitted successfully with {len(cleaned_data)} data items: {data_keys}"
)
logger
.
info
(
f
"RESULTS DEBUG:
Signal emitted successfully with {len(cleaned_data)} data items: {data_keys}"
)
else
:
else
:
logger
.
warning
(
f
"Prevented emission of invalid data: {cleaned_data}"
)
logger
.
warning
(
f
"
RESULTS DEBUG:
Prevented emission of invalid data: {cleaned_data}"
)
def
_clean_data
(
self
,
data
:
Dict
[
str
,
Any
])
->
Dict
[
str
,
Any
]:
def
_clean_data
(
self
,
data
:
Dict
[
str
,
Any
])
->
Dict
[
str
,
Any
]:
"""Clean data by removing null/undefined values before sending to JavaScript"""
"""Clean data by removing null/undefined values before sending to JavaScript"""
...
@@ -204,13 +210,19 @@ class OverlayWebChannel(QObject):
...
@@ -204,13 +210,19 @@ class OverlayWebChannel(QObject):
def
getCurrentData
(
self
)
->
str
:
def
getCurrentData
(
self
)
->
str
:
"""Provide current overlay data to JavaScript via WebChannel"""
"""Provide current overlay data to JavaScript via WebChannel"""
try
:
try
:
logger
.
debug
(
"OverlayWebChannel:
getCurrentData called"
)
logger
.
info
(
"RESULTS DEBUG: OverlayWebChannel
getCurrentData called"
)
# Return current overlay data
# Return current overlay data
current_data
=
dict
(
self
.
overlay_data
)
current_data
=
dict
(
self
.
overlay_data
)
logger
.
debug
(
f
"OverlayWebChannel: Returning current data: {current_data}"
)
logger
.
info
(
f
"RESULTS DEBUG: Current overlay_data keys: {list(current_data.keys())}"
)
return
json
.
dumps
(
current_data
)
logger
.
info
(
f
"RESULTS DEBUG: Current overlay_data: {current_data}"
)
logger
.
info
(
f
"RESULTS DEBUG: Returning current data to JavaScript: {current_data}"
)
json_result
=
json
.
dumps
(
current_data
)
logger
.
info
(
f
"RESULTS DEBUG: JSON result length: {len(json_result)}"
)
return
json_result
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"OverlayWebChannel: Failed to get current data: {e}"
)
logger
.
error
(
f
"RESULTS DEBUG: OverlayWebChannel Failed to get current data: {e}"
)
import
traceback
logger
.
error
(
f
"RESULTS DEBUG: Full traceback: {traceback.format_exc()}"
)
return
json
.
dumps
({})
return
json
.
dumps
({})
@
pyqtSlot
(
result
=
str
)
@
pyqtSlot
(
result
=
str
)
...
...
mbetterclient/qt_player/templates/match.html
View file @
0d4bd057
...
@@ -634,25 +634,37 @@
...
@@ -634,25 +634,37 @@
// Handle timer updates from match_timer
// Handle timer updates from match_timer
const
timerData
=
data
.
timer_update
;
const
timerData
=
data
.
timer_update
;
if
(
timerData
.
running
&&
timerData
.
remaining_seconds
!==
undefined
)
{
if
(
timerData
.
running
&&
timerData
.
remaining_seconds
!==
undefined
)
{
// Format remaining time
// Clear any existing countdown
const
minutes
=
Math
.
floor
(
timerData
.
remaining_seconds
/
60
);
if
(
countdownInterval
)
{
const
seconds
=
timerData
.
remaining_seconds
%
60
;
clearInterval
(
countdownInterval
);
const
timeString
=
`
${
minutes
.
toString
().
padStart
(
2
,
'0'
)}
:
${
seconds
.
toString
().
padStart
(
2
,
'0'
)}
`
;
countdownInterval
=
null
;
}
const
countdownTimer
=
document
.
getElementById
(
'countdownTimer'
);
// Set next match start time
if
(
countdownTimer
)
{
nextMatchStartTime
=
new
Date
(
Date
.
now
()
+
(
timerData
.
remaining_seconds
*
1000
));
countdownTimer
.
textContent
=
timeString
;
countdownTimer
.
className
=
'countdown-timer'
;
countdownTimer
.
style
.
display
=
'block'
;
// Add warning/urgent classes based on time remaining
// Show next match info
if
(
timerData
.
remaining_seconds
<=
60
)
{
// 1 minute
const
nextMatchInfo
=
document
.
getElementById
(
'nextMatchInfo'
);
countdownTimer
.
className
=
'countdown-timer urgent'
;
if
(
nextMatchInfo
)
{
}
else
if
(
timerData
.
remaining_seconds
<=
300
)
{
// 5 minutes
nextMatchInfo
.
textContent
=
`Next match starting in:`
;
countdownTimer
.
className
=
'countdown-timer warning'
;
nextMatchInfo
.
style
.
display
=
'block'
;
}
else
{
countdownTimer
.
className
=
'countdown-timer'
;
}
}
// Start countdown
updateCountdown
();
countdownInterval
=
setInterval
(
updateCountdown
,
1000
);
console
.
log
(
'🔍 DEBUG: Countdown started from timer update'
);
}
else
{
// No active timer, hide countdown
const
nextMatchInfo
=
document
.
getElementById
(
'nextMatchInfo'
);
const
countdownTimer
=
document
.
getElementById
(
'countdownTimer'
);
if
(
nextMatchInfo
)
nextMatchInfo
.
style
.
display
=
'none'
;
if
(
countdownTimer
)
countdownTimer
.
style
.
display
=
'none'
;
// Clear countdown interval
if
(
countdownInterval
)
{
clearInterval
(
countdownInterval
);
countdownInterval
=
null
;
}
}
}
}
}
}
...
@@ -661,25 +673,37 @@
...
@@ -661,25 +673,37 @@
// Handle timer updates from match_timer
// Handle timer updates from match_timer
const
timerData
=
data
.
timer_update
;
const
timerData
=
data
.
timer_update
;
if
(
timerData
.
running
&&
timerData
.
remaining_seconds
!==
undefined
)
{
if
(
timerData
.
running
&&
timerData
.
remaining_seconds
!==
undefined
)
{
// Format remaining time
// Clear any existing countdown
const
minutes
=
Math
.
floor
(
timerData
.
remaining_seconds
/
60
);
if
(
countdownInterval
)
{
const
seconds
=
timerData
.
remaining_seconds
%
60
;
clearInterval
(
countdownInterval
);
const
timeString
=
`
${
minutes
.
toString
().
padStart
(
2
,
'0'
)}
:
${
seconds
.
toString
().
padStart
(
2
,
'0'
)}
`
;
countdownInterval
=
null
;
}
const
countdownTimer
=
document
.
getElementById
(
'countdownTimer'
);
// Set next match start time
if
(
countdownTimer
)
{
nextMatchStartTime
=
new
Date
(
Date
.
now
()
+
(
timerData
.
remaining_seconds
*
1000
));
countdownTimer
.
textContent
=
timeString
;
countdownTimer
.
className
=
'countdown-timer'
;
countdownTimer
.
style
.
display
=
'block'
;
// Add warning/urgent classes based on time remaining
// Show next match info
if
(
timerData
.
remaining_seconds
<=
60
)
{
// 1 minute
const
nextMatchInfo
=
document
.
getElementById
(
'nextMatchInfo'
);
countdownTimer
.
className
=
'countdown-timer urgent'
;
if
(
nextMatchInfo
)
{
}
else
if
(
timerData
.
remaining_seconds
<=
300
)
{
// 5 minutes
nextMatchInfo
.
textContent
=
`Next match starting in:`
;
countdownTimer
.
className
=
'countdown-timer warning'
;
nextMatchInfo
.
style
.
display
=
'block'
;
}
else
{
countdownTimer
.
className
=
'countdown-timer'
;
}
}
// Start countdown
updateCountdown
();
countdownInterval
=
setInterval
(
updateCountdown
,
1000
);
console
.
log
(
'🔍 DEBUG: Countdown started from timer update'
);
}
else
{
// No active timer, hide countdown
const
nextMatchInfo
=
document
.
getElementById
(
'nextMatchInfo'
);
const
countdownTimer
=
document
.
getElementById
(
'countdownTimer'
);
if
(
nextMatchInfo
)
nextMatchInfo
.
style
.
display
=
'none'
;
if
(
countdownTimer
)
countdownTimer
.
style
.
display
=
'none'
;
// Clear countdown interval
if
(
countdownInterval
)
{
clearInterval
(
countdownInterval
);
countdownInterval
=
null
;
}
}
}
}
}
}
...
@@ -1138,6 +1162,10 @@
...
@@ -1138,6 +1162,10 @@
console
.
log
(
'🔍 DEBUG: WebServerBaseUrl not received via WebChannel, proceeding with WebChannel data fetch'
);
console
.
log
(
'🔍 DEBUG: WebServerBaseUrl not received via WebChannel, proceeding with WebChannel data fetch'
);
}
}
// Check for timer state immediately when page loads
debugTime
(
'Checking timer state on page load'
);
getTimerStateAndStartCountdown
();
// Fetch fixture data directly from WebChannel
// Fetch fixture data directly from WebChannel
debugTime
(
'Fetching fixture data from WebChannel'
);
debugTime
(
'Fetching fixture data from WebChannel'
);
fetchFixtureData
();
fetchFixtureData
();
...
...
mbetterclient/qt_player/templates/results.html
View file @
0d4bd057
This diff is collapsed.
Click to expand it.
mbetterclient/web_dashboard/static/overlay.js
View file @
0d4bd057
...
@@ -47,6 +47,24 @@ class OverlayManager {
...
@@ -47,6 +47,24 @@ class OverlayManager {
});
});
}
}
// Connect dataUpdated signal for templates that need it (like results.html)
if
(
window
.
overlay
.
dataUpdated
)
{
window
.
overlay
.
dataUpdated
.
connect
((
data
)
=>
{
if
(
data
!==
null
&&
data
!==
undefined
)
{
// Call a global callback if it exists (for results.html and other templates)
if
(
window
.
onDataUpdated
)
{
window
.
onDataUpdated
(
data
);
}
console
.
log
(
'dataUpdated signal received:'
,
data
);
}
else
{
console
.
warn
(
'dataUpdated signal received null/undefined data'
);
}
});
}
// Mark WebChannel as ready
this
.
webChannelReady
=
true
;
// Process pending updates after full initialization
// Process pending updates after full initialization
setTimeout
(()
=>
this
.
processPendingUpdates
(),
100
);
setTimeout
(()
=>
this
.
processPendingUpdates
(),
100
);
console
.
log
(
'WebChannel connected and ready'
);
console
.
log
(
'WebChannel connected and ready'
);
...
@@ -80,6 +98,24 @@ class OverlayManager {
...
@@ -80,6 +98,24 @@ class OverlayManager {
}
}
});
});
// Connect dataUpdated signal for templates that need it (like results.html)
if
(
overlay
.
dataUpdated
)
{
overlay
.
dataUpdated
.
connect
((
data
)
=>
{
if
(
data
!==
null
&&
data
!==
undefined
)
{
// Call a global callback if it exists (for results.html and other templates)
if
(
window
.
onDataUpdated
)
{
window
.
onDataUpdated
(
data
);
}
console
.
log
(
'dataUpdated signal received:'
,
data
);
}
else
{
console
.
warn
(
'dataUpdated signal received null/undefined data'
);
}
});
}
// Mark WebChannel as ready
this
.
webChannelReady
=
true
;
// Process pending updates after full initialization
// Process pending updates after full initialization
setTimeout
(()
=>
this
.
processPendingUpdates
(),
100
);
setTimeout
(()
=>
this
.
processPendingUpdates
(),
100
);
console
.
log
(
'WebChannel connected via fallback method'
);
console
.
log
(
'WebChannel connected via fallback method'
);
...
...
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