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
a596debf
Commit
a596debf
authored
Dec 03, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
- Removed native overlay
parent
d697940f
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
586 additions
and
794 deletions
+586
-794
PyQt6_UPGRADE_SUMMARY.md
PyQt6_UPGRADE_SUMMARY.md
+9
-5
main.py
main.py
+0
-6
client.py
mbetterclient/api_client/client.py
+7
-7
settings.py
mbetterclient/config/settings.py
+0
-1
application.py
mbetterclient/core/application.py
+0
-2
match_timer.py
mbetterclient/core/match_timer.py
+1
-1
screen_cast.py
mbetterclient/core/screen_cast.py
+5
-5
thread_manager.py
mbetterclient/core/thread_manager.py
+6
-5
__init__.py
mbetterclient/qt_player/__init__.py
+0
-3
overlay_engine.py
mbetterclient/qt_player/overlay_engine.py
+0
-492
player.py
mbetterclient/qt_player/player.py
+248
-224
template_watcher.py
mbetterclient/qt_player/template_watcher.py
+18
-9
app.py
mbetterclient/web_dashboard/app.py
+9
-8
overlay.js
mbetterclient/web_dashboard/static/overlay.js
+283
-26
No files found.
PyQt6_UPGRADE_SUMMARY.md
View file @
a596debf
...
@@ -33,10 +33,11 @@ Successfully replaced the PyQt5 video player implementation with a comprehensive
...
@@ -33,10 +33,11 @@ Successfully replaced the PyQt5 video player implementation with a comprehensive
-
**Responsive Design**
: Automatic scaling for different resolutions
-
**Responsive Design**
: Automatic scaling for different resolutions
-
**GSAP-ready Structure**
: Animation framework integration support
-
**GSAP-ready Structure**
: Animation framework integration support
### 3. Legacy Compatibility (mbetterclient/qt_player/overlay_engine.py)
### 3. Legacy Removal (overlay_engine.py)
**UPDATED**
PyQt5 → PyQt6 imports and constants for compatibility:
**REMOVED**
the legacy native overlay engine entirely:
-
Updated Qt enums to PyQt6 format (Qt.AlignLeft → Qt.AlignmentFlag.AlignLeft)
-
The native Qt overlay implementation was replaced by the superior QWebEngineView system
-
Maintained backward compatibility for any remaining legacy code
-
All overlay functionality now uses HTML/CSS/JavaScript templates exclusively
-
Removed OverlayEngine and OverlayRenderer classes as they are no longer needed
### 4. Message Bus Integration
### 4. Message Bus Integration
**ENHANCED**
message handling for complete thread communication:
**ENHANCED**
message handling for complete thread communication:
...
@@ -135,7 +136,10 @@ message_bus.publish(overlay_message)
...
@@ -135,7 +136,10 @@ message_bus.publish(overlay_message)
### Modified Files:
### Modified Files:
-
`mbetterclient/qt_player/player.py`
-
**COMPLETELY REPLACED**
with PyQt6 implementation
-
`mbetterclient/qt_player/player.py`
-
**COMPLETELY REPLACED**
with PyQt6 implementation
-
`mbetterclient/qt_player/overlay_engine.py`
- Updated PyQt5 → PyQt6 imports
-
`mbetterclient/qt_player/__init__.py`
- Removed legacy overlay engine imports
### Removed Files:
-
`mbetterclient/qt_player/overlay_engine.py`
- Legacy native overlay implementation removed
### Unchanged Files:
### Unchanged Files:
-
`mbetterclient/core/application.py`
- Works seamlessly with new implementation
-
`mbetterclient/core/application.py`
- Works seamlessly with new implementation
...
...
main.py
View file @
a596debf
...
@@ -173,11 +173,6 @@ Examples:
...
@@ -173,11 +173,6 @@ Examples:
help
=
'Disable web dashboard (PyQt interface only)'
help
=
'Disable web dashboard (PyQt interface only)'
)
)
parser
.
add_argument
(
'--native-overlay'
,
action
=
'store_true'
,
help
=
'Use native Qt overlay instead of QWebEngineView (prevents freezing on some systems)'
)
# Screen cast options
# Screen cast options
parser
.
add_argument
(
parser
.
add_argument
(
...
@@ -275,7 +270,6 @@ def main():
...
@@ -275,7 +270,6 @@ def main():
settings
.
debug_overlay
=
args
.
debug_overlay
settings
.
debug_overlay
=
args
.
debug_overlay
settings
.
enable_qt
=
not
args
.
no_qt
settings
.
enable_qt
=
not
args
.
no_qt
settings
.
enable_web
=
not
args
.
no_web
settings
.
enable_web
=
not
args
.
no_web
settings
.
qt
.
use_native_overlay
=
args
.
native_overlay
# Timer settings
# Timer settings
if
args
.
start_timer
is
not
None
:
if
args
.
start_timer
is
not
None
:
...
...
mbetterclient/api_client/client.py
View file @
a596debf
...
@@ -922,27 +922,27 @@ class APIClient(ThreadedComponent):
...
@@ -922,27 +922,27 @@ class APIClient(ThreadedComponent):
self
.
message_bus
.
publish
(
ready_message
)
self
.
message_bus
.
publish
(
ready_message
)
# Main execution loop
# Main execution loop
while
self
.
running
:
while
self
.
running
and
not
self
.
shutdown_event
.
is_set
()
:
try
:
try
:
# Update heartbeat at the start of each loop
# Update heartbeat at the start of each loop
self
.
heartbeat
()
self
.
heartbeat
()
# Process messages
# Process messages
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
if
message
:
if
message
:
self
.
_process_message
(
message
)
self
.
_process_message
(
message
)
# Update heartbeat before potentially long operations
# Update heartbeat before potentially long operations
self
.
heartbeat
()
self
.
heartbeat
()
# Execute scheduled API requests
# Execute scheduled API requests
self
.
_execute_scheduled_requests
()
self
.
_execute_scheduled_requests
()
# Update heartbeat after operations
# Update heartbeat after operations
self
.
heartbeat
()
self
.
heartbeat
()
time
.
sleep
(
1.0
)
time
.
sleep
(
1.0
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"APIClient run loop error: {e}"
)
logger
.
error
(
f
"APIClient run loop error: {e}"
)
# Update heartbeat even in error cases
# Update heartbeat even in error cases
...
...
mbetterclient/config/settings.py
View file @
a596debf
...
@@ -228,7 +228,6 @@ class QtConfig:
...
@@ -228,7 +228,6 @@ class QtConfig:
overlay_enabled
:
bool
=
True
overlay_enabled
:
bool
=
True
default_template
:
str
=
"news_template"
default_template
:
str
=
"news_template"
overlay_opacity
:
float
=
0.9
overlay_opacity
:
float
=
0.9
use_native_overlay
:
bool
=
False
# Use native Qt widgets instead of QWebEngineView
# Performance settings
# Performance settings
hardware_acceleration
:
bool
=
True
hardware_acceleration
:
bool
=
True
...
...
mbetterclient/core/application.py
View file @
a596debf
...
@@ -133,8 +133,6 @@ class MbetterClientApplication:
...
@@ -133,8 +133,6 @@ class MbetterClientApplication:
stored_settings
.
enable_web
=
self
.
settings
.
enable_web
stored_settings
.
enable_web
=
self
.
settings
.
enable_web
stored_settings
.
enable_screen_cast
=
self
.
settings
.
enable_screen_cast
# Preserve screen cast setting
stored_settings
.
enable_screen_cast
=
self
.
settings
.
enable_screen_cast
# Preserve screen cast setting
# Preserve command line Qt overlay setting
stored_settings
.
qt
.
use_native_overlay
=
self
.
settings
.
qt
.
use_native_overlay
# Preserve command line debug settings
# Preserve command line debug settings
stored_settings
.
debug_overlay
=
self
.
settings
.
debug_overlay
stored_settings
.
debug_overlay
=
self
.
settings
.
debug_overlay
...
...
mbetterclient/core/match_timer.py
View file @
a596debf
...
@@ -63,7 +63,7 @@ class MatchTimerComponent(ThreadedComponent):
...
@@ -63,7 +63,7 @@ class MatchTimerComponent(ThreadedComponent):
"""Main timer loop"""
"""Main timer loop"""
logger
.
info
(
"MatchTimer component started"
)
logger
.
info
(
"MatchTimer component started"
)
while
self
.
running
:
while
self
.
running
and
not
self
.
shutdown_event
.
is_set
()
:
try
:
try
:
# Process any pending messages first
# Process any pending messages first
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
0.1
)
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
0.1
)
...
...
mbetterclient/core/screen_cast.py
View file @
a596debf
...
@@ -183,21 +183,21 @@ class ScreenCastComponent(ThreadedComponent):
...
@@ -183,21 +183,21 @@ class ScreenCastComponent(ThreadedComponent):
self
.
_connect_chromecast
()
self
.
_connect_chromecast
()
# Main loop - monitor and restart capture if needed
# Main loop - monitor and restart capture if needed
while
self
.
running
:
while
self
.
running
and
not
self
.
shutdown_event
.
is_set
()
:
try
:
try
:
# Process messages
# Process messages
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
if
message
:
if
message
:
self
.
_process_message
(
message
)
self
.
_process_message
(
message
)
# Check capture health
# Check capture health
self
.
_check_capture_health
()
self
.
_check_capture_health
()
# Update heartbeat
# Update heartbeat
self
.
heartbeat
()
self
.
heartbeat
()
time
.
sleep
(
1.0
)
time
.
sleep
(
1.0
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"ScreenCast run loop error: {e}"
)
logger
.
error
(
f
"ScreenCast run loop error: {e}"
)
time
.
sleep
(
1.0
)
time
.
sleep
(
1.0
)
...
...
mbetterclient/core/thread_manager.py
View file @
a596debf
...
@@ -233,10 +233,11 @@ class ThreadManager:
...
@@ -233,10 +233,11 @@ class ThreadManager:
def
stop_all
(
self
,
timeout
:
float
=
10.0
)
->
bool
:
def
stop_all
(
self
,
timeout
:
float
=
10.0
)
->
bool
:
"""Stop all components"""
"""Stop all components"""
logger
.
info
(
"Stopping all components..."
)
logger
.
info
(
"Stopping all components..."
)
success
=
True
success
=
True
stop_timeout
=
timeout
/
max
(
len
(
self
.
components
),
1
)
# Distribute timeout
# Use at least 8 seconds per component, but distribute total timeout
stop_timeout
=
max
(
timeout
/
max
(
len
(
self
.
components
),
1
),
8.0
)
with
self
.
_lock
:
with
self
.
_lock
:
for
name
,
component
in
self
.
components
.
items
():
for
name
,
component
in
self
.
components
.
items
():
try
:
try
:
...
@@ -248,12 +249,12 @@ class ThreadManager:
...
@@ -248,12 +249,12 @@ class ThreadManager:
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Exception stopping component {name}: {e}"
)
logger
.
error
(
f
"Exception stopping component {name}: {e}"
)
success
=
False
success
=
False
if
success
:
if
success
:
logger
.
info
(
"All components stopped successfully"
)
logger
.
info
(
"All components stopped successfully"
)
else
:
else
:
logger
.
warning
(
"Some components failed to stop cleanly"
)
logger
.
warning
(
"Some components failed to stop cleanly"
)
return
success
return
success
def
restart_component
(
self
,
name
:
str
,
timeout
:
float
=
5.0
)
->
bool
:
def
restart_component
(
self
,
name
:
str
,
timeout
:
float
=
5.0
)
->
bool
:
...
...
mbetterclient/qt_player/__init__.py
View file @
a596debf
...
@@ -3,13 +3,10 @@ PyQt video player with overlay templates for MbetterClient
...
@@ -3,13 +3,10 @@ PyQt video player with overlay templates for MbetterClient
"""
"""
from
.player
import
QtVideoPlayer
from
.player
import
QtVideoPlayer
from
.overlay_engine
import
OverlayEngine
,
OverlayRenderer
from
.templates
import
TemplateManager
,
NewsTemplate
from
.templates
import
TemplateManager
,
NewsTemplate
__all__
=
[
__all__
=
[
'QtVideoPlayer'
,
'QtVideoPlayer'
,
'OverlayEngine'
,
'OverlayRenderer'
,
'TemplateManager'
,
'TemplateManager'
,
'NewsTemplate'
'NewsTemplate'
]
]
\ No newline at end of file
mbetterclient/qt_player/overlay_engine.py
deleted
100644 → 0
View file @
d697940f
"""
Overlay engine for rendering dynamic overlays on video content
NOTE: This is legacy code - the new PyQt6 implementation uses QWebEngineView for overlays
"""
import
time
import
logging
from
typing
import
Dict
,
Any
,
Optional
,
List
,
Tuple
from
pathlib
import
Path
from
PyQt6.QtCore
import
Qt
,
QRect
,
QPoint
,
QTimer
,
QPropertyAnimation
,
QEasingCurve
from
PyQt6.QtGui
import
(
QPainter
,
QPen
,
QBrush
,
QColor
,
QFont
,
QPixmap
,
QFontMetrics
,
QLinearGradient
,
QRadialGradient
,
QPolygon
)
from
PyQt6.QtWidgets
import
QGraphicsEffect
logger
=
logging
.
getLogger
(
__name__
)
class
OverlayElement
:
"""Base class for overlay elements"""
def
__init__
(
self
,
element_id
:
str
,
element_type
:
str
,
config
:
Dict
[
str
,
Any
]):
self
.
id
=
element_id
self
.
type
=
element_type
self
.
config
=
config
self
.
visible
=
config
.
get
(
'visible'
,
True
)
self
.
opacity
=
config
.
get
(
'opacity'
,
1.0
)
self
.
x
=
config
.
get
(
'x'
,
0
)
self
.
y
=
config
.
get
(
'y'
,
0
)
self
.
width
=
config
.
get
(
'width'
,
100
)
self
.
height
=
config
.
get
(
'height'
,
50
)
self
.
z_index
=
config
.
get
(
'z_index'
,
0
)
# Animation properties
self
.
animation_config
=
config
.
get
(
'animation'
,
{})
self
.
animation_start_time
=
None
def
get_rect
(
self
)
->
QRect
:
"""Get element rectangle"""
return
QRect
(
int
(
self
.
x
),
int
(
self
.
y
),
int
(
self
.
width
),
int
(
self
.
height
))
def
update_animation
(
self
,
elapsed_time
:
float
):
"""Update animation state"""
if
not
self
.
animation_config
:
return
animation_type
=
self
.
animation_config
.
get
(
'type'
)
if
animation_type
==
'scroll'
:
self
.
_update_scroll_animation
(
elapsed_time
)
elif
animation_type
==
'fade'
:
self
.
_update_fade_animation
(
elapsed_time
)
elif
animation_type
==
'bounce'
:
self
.
_update_bounce_animation
(
elapsed_time
)
def
_update_scroll_animation
(
self
,
elapsed_time
:
float
):
"""Update scrolling text animation"""
speed
=
self
.
animation_config
.
get
(
'speed'
,
100
)
# pixels per second
direction
=
self
.
animation_config
.
get
(
'direction'
,
'left'
)
if
direction
==
'left'
:
self
.
x
-=
speed
*
elapsed_time
/
1000.0
elif
direction
==
'right'
:
self
.
x
+=
speed
*
elapsed_time
/
1000.0
elif
direction
==
'up'
:
self
.
y
-=
speed
*
elapsed_time
/
1000.0
elif
direction
==
'down'
:
self
.
y
+=
speed
*
elapsed_time
/
1000.0
def
_update_fade_animation
(
self
,
elapsed_time
:
float
):
"""Update fade animation"""
duration
=
self
.
animation_config
.
get
(
'duration'
,
2000
)
# ms
fade_type
=
self
.
animation_config
.
get
(
'fade_type'
,
'in'
)
if
self
.
animation_start_time
is
None
:
self
.
animation_start_time
=
elapsed_time
progress
=
(
elapsed_time
-
self
.
animation_start_time
)
/
duration
progress
=
max
(
0.0
,
min
(
1.0
,
progress
))
if
fade_type
==
'in'
:
self
.
opacity
=
progress
elif
fade_type
==
'out'
:
self
.
opacity
=
1.0
-
progress
elif
fade_type
==
'in_out'
:
if
progress
<=
0.5
:
self
.
opacity
=
progress
*
2
else
:
self
.
opacity
=
(
1.0
-
progress
)
*
2
def
_update_bounce_animation
(
self
,
elapsed_time
:
float
):
"""Update bounce animation"""
import
math
frequency
=
self
.
animation_config
.
get
(
'frequency'
,
1.0
)
# Hz
amplitude
=
self
.
animation_config
.
get
(
'amplitude'
,
10
)
# pixels
offset
=
amplitude
*
math
.
sin
(
2
*
math
.
pi
*
frequency
*
elapsed_time
/
1000.0
)
self
.
y
=
self
.
config
.
get
(
'y'
,
0
)
+
offset
def
render
(
self
,
painter
:
QPainter
,
canvas_rect
:
QRect
):
"""Render element (to be implemented by subclasses)"""
pass
class
RectangleElement
(
OverlayElement
):
"""Rectangle overlay element"""
def
__init__
(
self
,
element_id
:
str
,
config
:
Dict
[
str
,
Any
]):
super
()
.
__init__
(
element_id
,
'rectangle'
,
config
)
self
.
color
=
QColor
(
config
.
get
(
'color'
,
'#000000'
))
self
.
border_color
=
QColor
(
config
.
get
(
'border_color'
,
'#ffffff'
))
self
.
border_width
=
config
.
get
(
'border_width'
,
0
)
self
.
corner_radius
=
config
.
get
(
'corner_radius'
,
0
)
def
render
(
self
,
painter
:
QPainter
,
canvas_rect
:
QRect
):
"""Render rectangle"""
if
not
self
.
visible
or
self
.
opacity
<=
0
:
return
# Set opacity
painter
.
setOpacity
(
self
.
opacity
)
rect
=
self
.
get_rect
()
# Fill
if
self
.
color
.
alpha
()
>
0
:
brush
=
QBrush
(
self
.
color
)
painter
.
setBrush
(
brush
)
else
:
painter
.
setBrush
(
Qt
.
NoBrush
)
# Border
if
self
.
border_width
>
0
:
pen
=
QPen
(
self
.
border_color
,
self
.
border_width
)
painter
.
setPen
(
pen
)
else
:
painter
.
setPen
(
Qt
.
NoPen
)
# Draw
if
self
.
corner_radius
>
0
:
painter
.
drawRoundedRect
(
rect
,
self
.
corner_radius
,
self
.
corner_radius
)
else
:
painter
.
drawRect
(
rect
)
class
TextElement
(
OverlayElement
):
"""Text overlay element"""
def
__init__
(
self
,
element_id
:
str
,
config
:
Dict
[
str
,
Any
]):
super
()
.
__init__
(
element_id
,
'text'
,
config
)
self
.
text
=
config
.
get
(
'text'
,
''
)
self
.
font_family
=
config
.
get
(
'font_family'
,
'Arial'
)
self
.
font_size
=
config
.
get
(
'font_size'
,
16
)
self
.
font_weight
=
config
.
get
(
'font_weight'
,
'normal'
)
self
.
color
=
QColor
(
config
.
get
(
'color'
,
'#ffffff'
))
self
.
background_color
=
QColor
(
config
.
get
(
'background_color'
,
'transparent'
))
self
.
alignment
=
config
.
get
(
'alignment'
,
'left'
)
self
.
word_wrap
=
config
.
get
(
'word_wrap'
,
False
)
# Scrolling text properties
self
.
scroll_position
=
0
self
.
text_width
=
0
def
set_text
(
self
,
text
:
str
):
"""Update text content"""
self
.
text
=
text
self
.
scroll_position
=
0
# Reset scroll position
def
render
(
self
,
painter
:
QPainter
,
canvas_rect
:
QRect
):
"""Render text"""
if
not
self
.
visible
or
self
.
opacity
<=
0
or
not
self
.
text
:
return
# Set opacity
painter
.
setOpacity
(
self
.
opacity
)
# Setup font
font
=
QFont
(
self
.
font_family
,
self
.
font_size
)
if
self
.
font_weight
==
'bold'
:
font
.
setBold
(
True
)
painter
.
setFont
(
font
)
# Get text metrics
metrics
=
QFontMetrics
(
font
)
self
.
text_width
=
metrics
.
boundingRect
(
self
.
text
)
.
width
()
rect
=
self
.
get_rect
()
# Background
if
self
.
background_color
.
alpha
()
>
0
:
painter
.
fillRect
(
rect
,
self
.
background_color
)
# Text color
painter
.
setPen
(
self
.
color
)
# Handle scrolling animation
if
self
.
animation_config
.
get
(
'type'
)
==
'scroll'
:
self
.
_render_scrolling_text
(
painter
,
rect
,
metrics
)
else
:
self
.
_render_static_text
(
painter
,
rect
)
def
_render_scrolling_text
(
self
,
painter
:
QPainter
,
rect
:
QRect
,
metrics
:
QFontMetrics
):
"""Render scrolling text"""
if
self
.
text_width
<=
rect
.
width
():
# Text fits, no scrolling needed
self
.
_render_static_text
(
painter
,
rect
)
return
# Calculate scroll position
direction
=
self
.
animation_config
.
get
(
'direction'
,
'left'
)
if
direction
==
'left'
:
# Reset position when text fully scrolled out
if
self
.
x
<
-
self
.
text_width
:
self
.
x
=
rect
.
right
()
# Draw text at current position
text_rect
=
QRect
(
int
(
self
.
x
),
rect
.
y
(),
self
.
text_width
,
rect
.
height
())
else
:
text_rect
=
rect
# Draw text
alignment
=
Qt
.
AlignVCenter
if
self
.
alignment
==
'center'
:
alignment
|=
Qt
.
AlignHCenter
elif
self
.
alignment
==
'right'
:
alignment
|=
Qt
.
AlignRight
else
:
alignment
|=
Qt
.
AlignLeft
painter
.
drawText
(
text_rect
,
alignment
,
self
.
text
)
def
_render_static_text
(
self
,
painter
:
QPainter
,
rect
:
QRect
):
"""Render static text"""
# Text alignment
alignment
=
Qt
.
AlignVCenter
if
self
.
alignment
==
'center'
:
alignment
|=
Qt
.
AlignHCenter
elif
self
.
alignment
==
'right'
:
alignment
|=
Qt
.
AlignRight
else
:
alignment
|=
Qt
.
AlignLeft
if
self
.
word_wrap
:
alignment
|=
Qt
.
TextWordWrap
painter
.
drawText
(
rect
,
alignment
,
self
.
text
)
class
ImageElement
(
OverlayElement
):
"""Image overlay element"""
def
__init__
(
self
,
element_id
:
str
,
config
:
Dict
[
str
,
Any
]):
super
()
.
__init__
(
element_id
,
'image'
,
config
)
self
.
source
=
config
.
get
(
'source'
,
''
)
self
.
fit
=
config
.
get
(
'fit'
,
'contain'
)
# contain, cover, fill, scale-down
self
.
pixmap
:
Optional
[
QPixmap
]
=
None
self
.
_load_image
()
def
_load_image
(
self
):
"""Load image from source"""
try
:
if
self
.
source
:
# Handle both absolute and relative paths
if
Path
(
self
.
source
)
.
is_absolute
():
image_path
=
Path
(
self
.
source
)
else
:
# Relative to project root
project_root
=
Path
(
__file__
)
.
parent
.
parent
.
parent
image_path
=
project_root
/
self
.
source
if
image_path
.
exists
():
self
.
pixmap
=
QPixmap
(
str
(
image_path
))
logger
.
debug
(
f
"Loaded image: {image_path}"
)
else
:
logger
.
warning
(
f
"Image not found: {image_path}"
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to load image {self.source}: {e}"
)
def
set_source
(
self
,
source
:
str
):
"""Update image source"""
self
.
source
=
source
self
.
_load_image
()
def
render
(
self
,
painter
:
QPainter
,
canvas_rect
:
QRect
):
"""Render image"""
if
not
self
.
visible
or
self
.
opacity
<=
0
or
not
self
.
pixmap
:
return
# Set opacity
painter
.
setOpacity
(
self
.
opacity
)
rect
=
self
.
get_rect
()
# Scale pixmap based on fit mode
scaled_pixmap
=
self
.
_scale_pixmap
(
self
.
pixmap
,
rect
)
# Calculate position for centering if needed
if
self
.
fit
in
[
'contain'
,
'scale-down'
]:
x_offset
=
(
rect
.
width
()
-
scaled_pixmap
.
width
())
//
2
y_offset
=
(
rect
.
height
()
-
scaled_pixmap
.
height
())
//
2
draw_pos
=
QPoint
(
rect
.
x
()
+
x_offset
,
rect
.
y
()
+
y_offset
)
else
:
draw_pos
=
rect
.
topLeft
()
painter
.
drawPixmap
(
draw_pos
,
scaled_pixmap
)
def
_scale_pixmap
(
self
,
pixmap
:
QPixmap
,
target_rect
:
QRect
)
->
QPixmap
:
"""Scale pixmap according to fit mode"""
if
self
.
fit
==
'fill'
:
return
pixmap
.
scaled
(
target_rect
.
size
(),
Qt
.
IgnoreAspectRatio
,
Qt
.
SmoothTransformation
)
elif
self
.
fit
==
'cover'
:
return
pixmap
.
scaled
(
target_rect
.
size
(),
Qt
.
KeepAspectRatioByExpanding
,
Qt
.
SmoothTransformation
)
elif
self
.
fit
==
'contain'
:
return
pixmap
.
scaled
(
target_rect
.
size
(),
Qt
.
KeepAspectRatio
,
Qt
.
SmoothTransformation
)
elif
self
.
fit
==
'scale-down'
:
if
pixmap
.
width
()
<=
target_rect
.
width
()
and
pixmap
.
height
()
<=
target_rect
.
height
():
return
pixmap
# No scaling needed
else
:
return
pixmap
.
scaled
(
target_rect
.
size
(),
Qt
.
KeepAspectRatio
,
Qt
.
SmoothTransformation
)
else
:
return
pixmap
class
OverlayRenderer
:
"""Manages rendering of overlay elements"""
def
__init__
(
self
):
self
.
elements
:
Dict
[
str
,
OverlayElement
]
=
{}
self
.
start_time
=
time
.
time
()
*
1000
# milliseconds
self
.
last_update_time
=
self
.
start_time
def
add_element
(
self
,
element
:
OverlayElement
):
"""Add overlay element"""
self
.
elements
[
element
.
id
]
=
element
def
remove_element
(
self
,
element_id
:
str
):
"""Remove overlay element"""
if
element_id
in
self
.
elements
:
del
self
.
elements
[
element_id
]
def
get_element
(
self
,
element_id
:
str
)
->
Optional
[
OverlayElement
]:
"""Get overlay element by ID"""
return
self
.
elements
.
get
(
element_id
)
def
clear_elements
(
self
):
"""Clear all elements"""
self
.
elements
.
clear
()
def
update_element_data
(
self
,
element_id
:
str
,
data
:
Dict
[
str
,
Any
]):
"""Update element data"""
element
=
self
.
get_element
(
element_id
)
if
element
:
if
element
.
type
==
'text'
and
'text'
in
data
:
element
.
set_text
(
data
[
'text'
])
elif
element
.
type
==
'image'
and
'source'
in
data
:
element
.
set_source
(
data
[
'source'
])
# Update other properties
for
key
,
value
in
data
.
items
():
if
hasattr
(
element
,
key
):
setattr
(
element
,
key
,
value
)
def
render
(
self
,
painter
:
QPainter
,
canvas_rect
:
QRect
):
"""Render all overlay elements"""
current_time
=
time
.
time
()
*
1000
elapsed_time
=
current_time
-
self
.
last_update_time
self
.
last_update_time
=
current_time
# Sort elements by z-index
sorted_elements
=
sorted
(
self
.
elements
.
values
(),
key
=
lambda
e
:
e
.
z_index
)
# Render elements
for
element
in
sorted_elements
:
try
:
# Update animations
element
.
update_animation
(
current_time
-
self
.
start_time
)
# Render element
element
.
render
(
painter
,
canvas_rect
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to render element {element.id}: {e}"
)
class
OverlayEngine
:
"""Main overlay engine"""
def
__init__
(
self
):
self
.
renderer
=
OverlayRenderer
()
self
.
template_config
:
Optional
[
Dict
[
str
,
Any
]]
=
None
self
.
overlay_data
:
Dict
[
str
,
Any
]
=
{}
self
.
playback_position
=
0
self
.
playback_duration
=
0
def
load_template
(
self
,
template_config
:
Dict
[
str
,
Any
],
overlay_data
:
Dict
[
str
,
Any
]
=
None
):
"""Load overlay template"""
try
:
self
.
template_config
=
template_config
self
.
overlay_data
=
overlay_data
or
{}
# Clear existing elements
self
.
renderer
.
clear_elements
()
# Create elements from template
elements_config
=
template_config
.
get
(
'elements'
,
[])
for
element_config
in
elements_config
:
element
=
self
.
_create_element
(
element_config
)
if
element
:
self
.
renderer
.
add_element
(
element
)
# Apply overlay data
self
.
_apply_overlay_data
()
logger
.
info
(
f
"Loaded template with {len(elements_config)} elements"
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to load template: {e}"
)
def
_create_element
(
self
,
config
:
Dict
[
str
,
Any
])
->
Optional
[
OverlayElement
]:
"""Create overlay element from configuration"""
element_type
=
config
.
get
(
'type'
)
element_id
=
config
.
get
(
'id'
)
if
not
element_type
or
not
element_id
:
logger
.
warning
(
"Element missing type or id"
)
return
None
try
:
if
element_type
==
'rectangle'
:
return
RectangleElement
(
element_id
,
config
)
elif
element_type
==
'text'
:
return
TextElement
(
element_id
,
config
)
elif
element_type
==
'image'
:
return
ImageElement
(
element_id
,
config
)
else
:
logger
.
warning
(
f
"Unknown element type: {element_type}"
)
return
None
except
Exception
as
e
:
logger
.
error
(
f
"Failed to create element {element_id}: {e}"
)
return
None
def
update_overlay_data
(
self
,
overlay_data
:
Dict
[
str
,
Any
]):
"""Update overlay data"""
self
.
overlay_data
.
update
(
overlay_data
)
self
.
_apply_overlay_data
()
def
_apply_overlay_data
(
self
):
"""Apply overlay data to elements"""
try
:
for
element_id
,
data
in
self
.
overlay_data
.
items
():
self
.
renderer
.
update_element_data
(
element_id
,
data
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to apply overlay data: {e}"
)
def
update_playback_position
(
self
,
position
:
int
,
duration
:
int
):
"""Update video playback position"""
self
.
playback_position
=
position
self
.
playback_duration
=
duration
def
render
(
self
,
painter
:
QPainter
,
canvas_rect
:
QRect
):
"""Render overlay"""
try
:
self
.
renderer
.
render
(
painter
,
canvas_rect
)
except
Exception
as
e
:
logger
.
error
(
f
"Overlay render failed: {e}"
)
def
get_element_by_id
(
self
,
element_id
:
str
)
->
Optional
[
OverlayElement
]:
"""Get element by ID"""
return
self
.
renderer
.
get_element
(
element_id
)
def
set_element_visibility
(
self
,
element_id
:
str
,
visible
:
bool
):
"""Set element visibility"""
element
=
self
.
get_element_by_id
(
element_id
)
if
element
:
element
.
visible
=
visible
def
set_element_text
(
self
,
element_id
:
str
,
text
:
str
):
"""Set text content for text element"""
element
=
self
.
get_element_by_id
(
element_id
)
if
element
and
element
.
type
==
'text'
:
element
.
set_text
(
text
)
def
set_element_image
(
self
,
element_id
:
str
,
image_source
:
str
):
"""Set image source for image element"""
element
=
self
.
get_element_by_id
(
element_id
)
if
element
and
element
.
type
==
'image'
:
element
.
set_source
(
image_source
)
\ No newline at end of file
mbetterclient/qt_player/player.py
View file @
a596debf
...
@@ -647,16 +647,44 @@ class OverlayWebView(QWebEngineView):
...
@@ -647,16 +647,44 @@ class OverlayWebView(QWebEngineView):
self
.
setup_web_view
()
self
.
setup_web_view
()
self
.
_setup_custom_scheme
()
self
.
_setup_custom_scheme
()
logger
.
info
(
f
"OverlayWebView initialized - builtin: {self.builtin_templates_dir}, uploaded: {self.uploaded_templates_dir}"
)
logger
.
info
(
f
"OverlayWebView initialized - builtin: {self.builtin_templates_dir}, uploaded: {self.uploaded_templates_dir}"
)
def
setup_web_view
(
self
):
def
setup_web_view
(
self
):
"""Setup web view with proper transparency for overlay"""
"""Setup web view with proper transparency for overlay"""
logger
.
info
(
"OverlayWebView.setup_web_view() - Starting setup"
)
logger
.
info
(
"OverlayWebView.setup_web_view() - Starting setup"
)
# Enhanced GPU detection and logging
# Enhanced GPU detection and logging
for VirtualBox compatibility
import
os
import
os
is_mesa
=
os
.
environ
.
get
(
'LIBGL_ALWAYS_SOFTWARE'
)
==
'1'
or
\
is_mesa
=
os
.
environ
.
get
(
'LIBGL_ALWAYS_SOFTWARE'
)
==
'1'
or
\
os
.
environ
.
get
(
'MESA_GL_VERSION_OVERRIDE'
)
is
not
None
os
.
environ
.
get
(
'MESA_GL_VERSION_OVERRIDE'
)
is
not
None
# Check if running in VirtualBox
is_virtualbox
=
False
try
:
with
open
(
'/sys/devices/virtual/dmi/id/sys_vendor'
,
'r'
)
as
f
:
vendor
=
f
.
read
()
.
strip
()
.
lower
()
if
'virtualbox'
in
vendor
or
'innotek'
in
vendor
:
is_virtualbox
=
True
logger
.
warning
(
"VIRTUALBOX ENVIRONMENT DETECTED - Overlay compatibility issues likely"
)
except
:
# Check for VirtualBox guest additions
try
:
import
subprocess
result
=
subprocess
.
run
([
'lsmod'
],
capture_output
=
True
,
text
=
True
,
timeout
=
2
)
if
'vboxvideo'
in
result
.
stdout
or
'vboxguest'
in
result
.
stdout
:
is_virtualbox
=
True
logger
.
warning
(
"VIRTUALBOX GUEST MODULES DETECTED - Overlay compatibility issues likely"
)
except
:
pass
if
is_virtualbox
:
logger
.
warning
(
"=== VIRTUALBOX OVERLAY DIAGNOSTICS ==="
)
logger
.
warning
(
"VirtualBox detected - common overlay issues:"
)
logger
.
warning
(
"1. GPU acceleration disabled by default"
)
logger
.
warning
(
"2. Limited OpenGL support"
)
logger
.
warning
(
"3. Qt WebEngine may fallback to software rendering"
)
logger
.
warning
(
"4. Transparency effects may not work"
)
logger
.
warning
(
"5. WebChannel communication may be unreliable"
)
# Log GPU-related environment variables
# Log GPU-related environment variables
gpu_env_vars
=
[
gpu_env_vars
=
[
'LIBGL_ALWAYS_SOFTWARE'
,
'LIBGL_ALWAYS_SOFTWARE'
,
...
@@ -666,7 +694,9 @@ class OverlayWebView(QWebEngineView):
...
@@ -666,7 +694,9 @@ class OverlayWebView(QWebEngineView):
'DISPLAY'
,
'DISPLAY'
,
'XDG_SESSION_TYPE'
,
'XDG_SESSION_TYPE'
,
'QTWEBENGINE_DISABLE_SANDBOX'
,
'QTWEBENGINE_DISABLE_SANDBOX'
,
'QTWEBENGINE_REMOTE_DEBUGGING'
'QTWEBENGINE_REMOTE_DEBUGGING'
,
'QTWEBENGINE_DISABLE_GPU'
,
'QT_OPENGL'
]
]
logger
.
info
(
"=== GPU Environment Check ==="
)
logger
.
info
(
"=== GPU Environment Check ==="
)
...
@@ -684,7 +714,7 @@ class OverlayWebView(QWebEngineView):
...
@@ -684,7 +714,7 @@ class OverlayWebView(QWebEngineView):
gpu_info
=
result
.
stdout
.
strip
()
.
split
(
','
)
gpu_info
=
result
.
stdout
.
strip
()
.
split
(
','
)
logger
.
info
(
f
"NVIDIA GPU detected: {gpu_info[0]}, Memory: {gpu_info[1]}/{gpu_info[2]}MB, Driver: {gpu_info[3]}"
)
logger
.
info
(
f
"NVIDIA GPU detected: {gpu_info[0]}, Memory: {gpu_info[1]}/{gpu_info[2]}MB, Driver: {gpu_info[3]}"
)
else
:
else
:
logger
.
warning
(
"nvidia-smi not available or failed"
)
logger
.
warning
(
"nvidia-smi not available or failed
- likely VirtualBox or no NVIDIA GPU
"
)
# Check GPU processes specifically
# Check GPU processes specifically
proc_result
=
subprocess
.
run
([
'nvidia-smi'
,
'pmon'
],
capture_output
=
True
,
text
=
True
,
timeout
=
5
)
proc_result
=
subprocess
.
run
([
'nvidia-smi'
,
'pmon'
],
capture_output
=
True
,
text
=
True
,
timeout
=
5
)
...
@@ -696,12 +726,12 @@ class OverlayWebView(QWebEngineView):
...
@@ -696,12 +726,12 @@ class OverlayWebView(QWebEngineView):
for
proc
in
gpu_processes
[:
3
]:
# Log first 3 processes
for
proc
in
gpu_processes
[:
3
]:
# Log first 3 processes
logger
.
info
(
f
"GPU Process: {proc}"
)
logger
.
info
(
f
"GPU Process: {proc}"
)
else
:
else
:
logger
.
warning
(
"NO GPU PROCESSES FOUND - This indicates GPU is not being utilized"
)
logger
.
warning
(
"NO GPU PROCESSES FOUND - This indicates GPU is not being utilized
(common in VirtualBox)
"
)
else
:
else
:
logger
.
warning
(
"Could not check GPU processes"
)
logger
.
warning
(
"Could not check GPU processes
- likely VirtualBox environment
"
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
warning
(
f
"Could not check NVIDIA GPU: {e}"
)
logger
.
warning
(
f
"Could not check NVIDIA GPU: {e}
- likely VirtualBox or no GPU
"
)
# Check OpenGL
# Check OpenGL
try
:
try
:
...
@@ -710,9 +740,9 @@ class OverlayWebView(QWebEngineView):
...
@@ -710,9 +740,9 @@ class OverlayWebView(QWebEngineView):
if
result
.
returncode
==
0
:
if
result
.
returncode
==
0
:
logger
.
info
(
f
"OpenGL renderer: {result.stdout.strip()}"
)
logger
.
info
(
f
"OpenGL renderer: {result.stdout.strip()}"
)
else
:
else
:
logger
.
warning
(
"Could not get OpenGL renderer info"
)
logger
.
warning
(
"Could not get OpenGL renderer info
- VirtualBox may not support OpenGL properly
"
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
warning
(
f
"Could not check OpenGL: {e}"
)
logger
.
warning
(
f
"Could not check OpenGL: {e}
- VirtualBox OpenGL issues likely
"
)
# Check Vulkan (Qt WebEngine can use Vulkan on some systems)
# Check Vulkan (Qt WebEngine can use Vulkan on some systems)
try
:
try
:
...
@@ -720,13 +750,22 @@ class OverlayWebView(QWebEngineView):
...
@@ -720,13 +750,22 @@ class OverlayWebView(QWebEngineView):
if
result
.
returncode
==
0
:
if
result
.
returncode
==
0
:
logger
.
info
(
"Vulkan is available"
)
logger
.
info
(
"Vulkan is available"
)
else
:
else
:
logger
.
info
(
"Vulkan not available or not configured"
)
logger
.
info
(
"Vulkan not available or not configured
- VirtualBox typically lacks Vulkan support
"
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
debug
(
f
"Vulkan check failed: {e}"
)
logger
.
debug
(
f
"Vulkan check failed: {e}"
)
logger
.
info
(
f
"Mesa software rendering detection: {is_mesa}"
)
logger
.
info
(
f
"Mesa software rendering detection: {is_mesa}"
)
if
is_mesa
:
if
is_mesa
:
logger
.
warning
(
"MESA SOFTWARE RENDERING DETECTED - GPU acceleration may be disabled"
)
logger
.
warning
(
"MESA SOFTWARE RENDERING DETECTED - GPU acceleration may be disabled (VirtualBox default)"
)
elif
is_virtualbox
:
logger
.
warning
(
"VirtualBox detected but Mesa not forced - Qt WebEngine may still fallback to software rendering"
)
# Check Qt WebEngine version and capabilities
try
:
from
PyQt6.QtWebEngineCore
import
QWebEngineProfile
logger
.
info
(
"Qt WebEngine is available"
)
except
ImportError
as
e
:
logger
.
error
(
f
"Qt WebEngine not available: {e} - overlays will not work"
)
# Set transparent background on the web page
# Set transparent background on the web page
page
=
self
.
page
()
page
=
self
.
page
()
...
@@ -764,14 +803,20 @@ class OverlayWebView(QWebEngineView):
...
@@ -764,14 +803,20 @@ class OverlayWebView(QWebEngineView):
logger
.
info
(
f
"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured"
)
logger
.
info
(
f
"OverlayWebView setup completed - Mesa: {is_mesa}, transparency configured"
)
# Setup WebChannel
# Setup WebChannel
logger
.
info
(
"Setting up WebChannel for overlay communication"
)
self
.
web_channel
=
QWebChannel
()
self
.
web_channel
=
QWebChannel
()
# Get message bus from parent window
# Get message bus from parent window
message_bus
=
None
message_bus
=
None
if
hasattr
(
self
.
parent
(),
'_message_bus'
):
if
hasattr
(
self
.
parent
(),
'_message_bus'
):
message_bus
=
self
.
parent
()
.
_message_bus
message_bus
=
self
.
parent
()
.
_message_bus
logger
.
info
(
"Message bus found for WebChannel"
)
else
:
logger
.
warning
(
"No message bus available for WebChannel - overlay communication may be limited"
)
self
.
overlay_channel
=
OverlayWebChannel
(
db_manager
=
self
.
db_manager
,
message_bus
=
message_bus
)
self
.
overlay_channel
=
OverlayWebChannel
(
db_manager
=
self
.
db_manager
,
message_bus
=
message_bus
)
self
.
web_channel
.
registerObject
(
"overlay"
,
self
.
overlay_channel
)
self
.
web_channel
.
registerObject
(
"overlay"
,
self
.
overlay_channel
)
page
.
setWebChannel
(
self
.
web_channel
)
page
.
setWebChannel
(
self
.
web_channel
)
logger
.
info
(
"WebChannel setup completed - overlay object registered as 'overlay'"
)
# Monitor GPU processes after WebEngine initialization
# Monitor GPU processes after WebEngine initialization
self
.
_monitor_gpu_processes
()
self
.
_monitor_gpu_processes
()
...
@@ -800,6 +845,8 @@ class OverlayWebView(QWebEngineView):
...
@@ -800,6 +845,8 @@ class OverlayWebView(QWebEngineView):
# Check Qt WebEngine GPU acceleration status
# Check Qt WebEngine GPU acceleration status
QTimer
.
singleShot
(
2000
,
self
.
_check_qt_webengine_gpu_status
)
QTimer
.
singleShot
(
2000
,
self
.
_check_qt_webengine_gpu_status
)
logger
.
info
(
"OverlayWebView setup completed successfully"
)
def
_enable_debug_console
(
self
,
ok
=
None
):
def
_enable_debug_console
(
self
,
ok
=
None
):
"""Enable debug console and ensure JavaScript overrides are active"""
"""Enable debug console and ensure JavaScript overrides are active"""
try
:
try
:
...
@@ -937,6 +984,20 @@ class OverlayWebView(QWebEngineView):
...
@@ -937,6 +984,20 @@ class OverlayWebView(QWebEngineView):
"""Load a specific template file, prioritizing uploaded templates"""
"""Load a specific template file, prioritizing uploaded templates"""
try
:
try
:
logger
.
info
(
f
"=== LOADING OVERLAY TEMPLATE: {template_name} ==="
)
logger
.
info
(
f
"=== LOADING OVERLAY TEMPLATE: {template_name} ==="
)
# Check if running in VirtualBox for additional diagnostics
is_virtualbox
=
False
try
:
with
open
(
'/sys/devices/virtual/dmi/id/sys_vendor'
,
'r'
)
as
f
:
vendor
=
f
.
read
()
.
strip
()
.
lower
()
if
'virtualbox'
in
vendor
or
'innotek'
in
vendor
:
is_virtualbox
=
True
except
:
pass
if
is_virtualbox
:
logger
.
warning
(
"VIRTUALBOX: Loading overlay template - potential compatibility issues"
)
if
self
.
debug_overlay
:
if
self
.
debug_overlay
:
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Starting template load - {template_name}"
)
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: Current page URL before load: {self.url().toString()}"
)
...
@@ -944,6 +1005,7 @@ class OverlayWebView(QWebEngineView):
...
@@ -944,6 +1005,7 @@ class OverlayWebView(QWebEngineView):
# CRITICAL FIX: Store visibility state before template load
# CRITICAL FIX: Store visibility state before template load
was_visible
=
self
.
isVisible
()
was_visible
=
self
.
isVisible
()
logger
.
info
(
f
"Overlay visibility before template load: {was_visible}"
)
# If no template name provided, use default
# If no template name provided, use default
if
not
template_name
:
if
not
template_name
:
...
@@ -983,6 +1045,16 @@ class OverlayWebView(QWebEngineView):
...
@@ -983,6 +1045,16 @@ class OverlayWebView(QWebEngineView):
if
template_path
and
template_path
.
exists
():
if
template_path
and
template_path
.
exists
():
logger
.
info
(
f
"TEMPLATE FOUND: {template_path} (source: {template_source})"
)
logger
.
info
(
f
"TEMPLATE FOUND: {template_path} (source: {template_source})"
)
# Check template file permissions and size
try
:
stat_info
=
template_path
.
stat
()
logger
.
info
(
f
"Template file size: {stat_info.st_size} bytes"
)
logger
.
info
(
f
"Template file permissions: {oct(stat_info.st_mode)}"
)
logger
.
info
(
f
"Template file readable: {template_path.stat().st_mode & 0o400 != 0}"
)
except
Exception
as
stat_error
:
logger
.
warning
(
f
"Could not check template file stats: {stat_error}"
)
if
self
.
debug_overlay
:
if
self
.
debug_overlay
:
logger
.
debug
(
f
"GREEN SCREEN DEBUG: About to load template file: {template_path}"
)
logger
.
debug
(
f
"GREEN SCREEN DEBUG: About to load template file: {template_path}"
)
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Template source: {template_source}"
)
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Template source: {template_source}"
)
...
@@ -1011,12 +1083,28 @@ class OverlayWebView(QWebEngineView):
...
@@ -1011,12 +1083,28 @@ class OverlayWebView(QWebEngineView):
self
.
_load_fallback_overlay
()
self
.
_load_fallback_overlay
()
return
return
# Check WebEngine page state before loading
page
=
self
.
page
()
if
page
:
logger
.
info
(
"WebEngine page state before template load:"
)
logger
.
info
(
f
" - Page URL: {page.url().toString()}"
)
logger
.
info
(
f
" - Page title: {page.title()}"
)
logger
.
info
(
f
" - Page background color: {page.backgroundColor()}"
)
logger
.
info
(
f
" - WebChannel available: {page.webChannel() is not None}"
)
else
:
logger
.
warning
(
"No WebEngine page available before template load"
)
logger
.
info
(
f
"LOADING TEMPLATE INTO WEBENGINE: {template_path}"
)
logger
.
info
(
f
"LOADING TEMPLATE INTO WEBENGINE: {template_path}"
)
load_start_time
=
time
.
time
()
self
.
load
(
QUrl
.
fromLocalFile
(
str
(
template_path
)))
self
.
load
(
QUrl
.
fromLocalFile
(
str
(
template_path
)))
load_end_time
=
time
.
time
()
logger
.
info
(
f
"Template load initiated in {load_end_time - load_start_time:.3f} seconds"
)
self
.
current_template
=
template_name
self
.
current_template
=
template_name
# CRITICAL FIX: Force visibility recovery after template load
# CRITICAL FIX: Force visibility recovery after template load
if
was_visible
and
not
self
.
isVisible
():
if
was_visible
and
not
self
.
isVisible
():
logger
.
warning
(
"Overlay lost visibility after template load, recovering"
)
if
self
.
debug_overlay
:
if
self
.
debug_overlay
:
logger
.
debug
(
f
"GREEN SCREEN FIX: Recovering overlay visibility after template load"
)
logger
.
debug
(
f
"GREEN SCREEN FIX: Recovering overlay visibility after template load"
)
self
.
show
()
self
.
show
()
...
@@ -1046,6 +1134,10 @@ class OverlayWebView(QWebEngineView):
...
@@ -1046,6 +1134,10 @@ class OverlayWebView(QWebEngineView):
if
self
.
debug_overlay
:
if
self
.
debug_overlay
:
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Template load initiated - {template_path}"
)
logger
.
debug
(
f
"GREEN SCREEN DEBUG: Template load initiated - {template_path}"
)
logger
.
info
(
f
"=== TEMPLATE LOAD COMPLETED: {template_path} (source: {template_source}) ==="
)
logger
.
info
(
f
"=== TEMPLATE LOAD COMPLETED: {template_path} (source: {template_source}) ==="
)
# Schedule a check for template load success
QTimer
.
singleShot
(
1000
,
lambda
:
self
.
_check_template_load_success
(
template_name
))
else
:
else
:
logger
.
error
(
f
"No template found: {template_name}"
)
logger
.
error
(
f
"No template found: {template_name}"
)
# Load fallback minimal overlay
# Load fallback minimal overlay
...
@@ -1364,10 +1456,86 @@ class OverlayWebView(QWebEngineView):
...
@@ -1364,10 +1456,86 @@ class OverlayWebView(QWebEngineView):
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"Failed to start GPU process monitoring: {e}"
)
logger
.
error
(
f
"Failed to start GPU process monitoring: {e}"
)
def
_check_template_load_success
(
self
,
expected_template
:
str
):
"""Check if template loaded successfully"""
try
:
logger
.
info
(
f
"=== TEMPLATE LOAD SUCCESS CHECK: {expected_template} ==="
)
page
=
self
.
page
()
if
page
:
current_url
=
page
.
url
()
.
toString
()
logger
.
info
(
f
"Current page URL: {current_url}"
)
logger
.
info
(
f
"Expected template: {expected_template}"
)
# Check if URL contains the expected template
if
expected_template
in
current_url
or
expected_template
.
replace
(
'.html'
,
''
)
in
current_url
:
logger
.
info
(
"✓ Template URL matches expected template"
)
else
:
logger
.
warning
(
f
"✗ Template URL mismatch - expected {expected_template}, got {current_url}"
)
# Check page title
title
=
page
.
title
()
logger
.
info
(
f
"Page title: '{title}'"
)
# Run JavaScript to check page content
page
.
runJavaScript
(
"""
console.log('TEMPLATE CHECK: Page content validation');
console.log('Document readyState:', document.readyState);
console.log('Body exists:', !!document.body);
if (document.body) {
console.log('Body children count:', document.body.children.length);
console.log('Body innerHTML length:', document.body.innerHTML.length);
console.log('Body background:', window.getComputedStyle(document.body).background);
console.log('Body display:', window.getComputedStyle(document.body).display);
console.log('Body visibility:', window.getComputedStyle(document.body).visibility);
// Check for critical overlay elements
const criticalElements = ['titleMain', 'titleSubtitle', 'tickerText'];
criticalElements.forEach(id => {
const el = document.getElementById(id);
console.log(`Element ${id}: ${el ? 'EXISTS' : 'MISSING'}`);
if (el) {
console.log(` - display: ${window.getComputedStyle(el).display}`);
console.log(` - visibility: ${window.getComputedStyle(el).visibility}`);
console.log(` - textContent: '${el.textContent}'`);
}
});
}
console.log('TEMPLATE CHECK: Validation complete');
"""
)
# Check WebChannel status
web_channel
=
page
.
webChannel
()
logger
.
info
(
f
"WebChannel available: {web_channel is not None}"
)
if
web_channel
:
logger
.
info
(
"WebChannel is properly configured"
)
else
:
logger
.
warning
(
"WebChannel not available - overlay communication will fail"
)
else
:
logger
.
error
(
"No WebEngine page available for template check"
)
logger
.
info
(
f
"=== END TEMPLATE LOAD SUCCESS CHECK ==="
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to check template load success: {e}"
)
def
_check_overlay_visibility
(
self
):
def
_check_overlay_visibility
(
self
):
"""Check overlay window visibility and positioning"""
"""Check overlay window visibility and positioning"""
try
:
try
:
logger
.
info
(
"=== OVERLAY VISIBILITY CHECK ==="
)
logger
.
info
(
"=== OVERLAY VISIBILITY CHECK ==="
)
# Check if running in VirtualBox
is_virtualbox
=
False
try
:
with
open
(
'/sys/devices/virtual/dmi/id/sys_vendor'
,
'r'
)
as
f
:
vendor
=
f
.
read
()
.
strip
()
.
lower
()
if
'virtualbox'
in
vendor
or
'innotek'
in
vendor
:
is_virtualbox
=
True
logger
.
warning
(
"VIRTUALBOX: Running overlay visibility check"
)
except
:
pass
logger
.
info
(
f
"Overlay window exists: {self.parent() is not None}"
)
logger
.
info
(
f
"Overlay window exists: {self.parent() is not None}"
)
if
self
.
parent
():
if
self
.
parent
():
parent
=
self
.
parent
()
parent
=
self
.
parent
()
...
@@ -1380,6 +1548,15 @@ class OverlayWebView(QWebEngineView):
...
@@ -1380,6 +1548,15 @@ class OverlayWebView(QWebEngineView):
logger
.
info
(
f
"Overlay WebView size: {self.size()}"
)
logger
.
info
(
f
"Overlay WebView size: {self.size()}"
)
logger
.
info
(
f
"Overlay WebView pos: {self.pos()}"
)
logger
.
info
(
f
"Overlay WebView pos: {self.pos()}"
)
# Check if overlay is properly positioned over parent
if
self
.
parent
():
parent_geo
=
self
.
parent
()
.
geometry
()
overlay_geo
=
self
.
geometry
()
if
parent_geo
==
overlay_geo
:
logger
.
info
(
"✓ Overlay properly positioned over parent window"
)
else
:
logger
.
warning
(
f
"✗ Overlay positioning mismatch - parent: {parent_geo}, overlay: {overlay_geo}"
)
# Check WebEngine page status
# Check WebEngine page status
page
=
self
.
page
()
page
=
self
.
page
()
if
page
:
if
page
:
...
@@ -1389,11 +1566,30 @@ class OverlayWebView(QWebEngineView):
...
@@ -1389,11 +1566,30 @@ class OverlayWebView(QWebEngineView):
# Check if page has content
# Check if page has content
page
.
runJavaScript
(
"""
page
.
runJavaScript
(
"""
console.log('OVERLAY VISIBILITY: Checking page content');
console.log('OVERLAY VISIBILITY: Checking page content');
console.log('Document readyState:', document.readyState);
console.log('Body exists:', !!document.body);
console.log('Body exists:', !!document.body);
if (document.body) {
if (document.body) {
console.log('Body innerHTML length:', document.body.innerHTML.length);
console.log('Body innerHTML length:', document.body.innerHTML.length);
console.log('Body background:', window.getComputedStyle(document.body).background);
console.log('Body background:', window.getComputedStyle(document.body).background);
console.log('Body opacity:', window.getComputedStyle(document.body).opacity);
console.log('Body opacity:', window.getComputedStyle(document.body).opacity);
console.log('Body display:', window.getComputedStyle(document.body).display);
console.log('Body visibility:', window.getComputedStyle(document.body).visibility);
// Check for VirtualBox-specific issues
console.log('Window devicePixelRatio:', window.devicePixelRatio);
console.log('Canvas support:', !!document.createElement('canvas').getContext);
console.log('WebGL support check:');
try {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
console.log(' WebGL available:', !!gl);
if (gl) {
console.log(' WebGL renderer:', gl.getParameter(gl.RENDERER));
console.log(' WebGL vendor:', gl.getParameter(gl.VENDOR));
}
} catch (e) {
console.log(' WebGL check failed:', e.message);
}
}
}
console.log('OVERLAY VISIBILITY: Page check complete');
console.log('OVERLAY VISIBILITY: Page check complete');
"""
)
"""
)
...
@@ -1411,10 +1607,35 @@ class OverlayWebView(QWebEngineView):
...
@@ -1411,10 +1607,35 @@ class OverlayWebView(QWebEngineView):
logger
.
info
(
f
"GPU Process: {proc}"
)
logger
.
info
(
f
"GPU Process: {proc}"
)
else
:
else
:
logger
.
warning
(
"NO Qt WebEngine GPU processes found - overlay may not be using GPU acceleration"
)
logger
.
warning
(
"NO Qt WebEngine GPU processes found - overlay may not be using GPU acceleration"
)
if
is_virtualbox
:
logger
.
warning
(
"VIRTUALBOX: This is expected - VirtualBox typically disables GPU acceleration"
)
else
:
else
:
logger
.
warning
(
"Could not check GPU processes for Qt WebEngine"
)
logger
.
warning
(
"Could not check GPU processes for Qt WebEngine"
)
if
is_virtualbox
:
logger
.
warning
(
"VIRTUALBOX: GPU process check failed - common in VirtualBox environment"
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
debug
(
f
"GPU process check failed: {e}"
)
logger
.
debug
(
f
"GPU process check failed: {e}"
)
if
is_virtualbox
:
logger
.
warning
(
"VIRTUALBOX: GPU monitoring not available - this is normal"
)
# Check for VirtualBox-specific display issues
if
is_virtualbox
:
logger
.
info
(
"VIRTUALBOX: Checking for display compatibility issues"
)
try
:
import
os
display
=
os
.
environ
.
get
(
'DISPLAY'
,
'not set'
)
logger
.
info
(
f
"VIRTUALBOX: DISPLAY environment: {display}"
)
# Check if X11 forwarding is working
if
display
.
startswith
(
':'
):
logger
.
info
(
"VIRTUALBOX: Local X11 display detected"
)
elif
display
.
startswith
(
'localhost:'
)
or
display
.
startswith
(
'127.0.0.1:'
):
logger
.
warning
(
"VIRTUALBOX: X11 forwarding detected - may cause overlay issues"
)
else
:
logger
.
warning
(
"VIRTUALBOX: Unusual DISPLAY setting - may indicate display issues"
)
except
Exception
as
e
:
logger
.
debug
(
f
"VIRTUALBOX: Display check failed: {e}"
)
logger
.
info
(
"=== END OVERLAY VISIBILITY CHECK ==="
)
logger
.
info
(
"=== END OVERLAY VISIBILITY CHECK ==="
)
...
@@ -1520,205 +1741,15 @@ class OverlayWebView(QWebEngineView):
...
@@ -1520,205 +1741,15 @@ class OverlayWebView(QWebEngineView):
class
NativeOverlayWidget
(
QWidget
):
"""Native Qt overlay widget - no WebEngine to prevent freezing"""
def
__init__
(
self
,
parent
=
None
):
super
()
.
__init__
(
parent
)
# Initialize overlay_data BEFORE setup_ui() to prevent attribute errors
self
.
overlay_data
=
{
'title'
:
'MbetterClient PyQt6 Player'
,
'subtitle'
:
'Ready for Content'
,
'ticker'
:
'Welcome to MbetterClient • Professional Video Overlay System • Real-time Updates'
,
'currentTime'
:
'00:00:00'
}
# Add overlay_channel as None for compatibility with WebEngine interface
self
.
overlay_channel
=
None
self
.
setup_ui
()
logger
.
info
(
"NativeOverlayWidget initialized"
)
def
setup_ui
(
self
):
"""Setup native Qt overlay widgets with forced visibility"""
logger
.
debug
(
"NativeOverlayWidget.setup_ui() - Starting"
)
# FORCE widget to be completely visible with bright background
self
.
setStyleSheet
(
"""
NativeOverlayWidget {
background-color: rgba(255, 0, 255, 200);
border: 5px solid cyan;
}
"""
)
# Ensure widget is visible
self
.
setVisible
(
True
)
self
.
setAutoFillBackground
(
True
)
self
.
raise_
()
# Bring to front
# Main title label - ENHANCED VISIBILITY
self
.
title_label
=
QLabel
()
self
.
title_label
.
setStyleSheet
(
"""
QLabel {
color: white;
font-size: 32px;
font-weight: bold;
background: rgba(0,0,0,0.8);
padding: 15px;
border-radius: 8px;
border: 2px solid rgba(255,255,255,0.3);
text-shadow: 2px 2px 4px rgba(0,0,0,1.0);
}
"""
)
self
.
title_label
.
setAlignment
(
Qt
.
AlignmentFlag
.
AlignCenter
)
self
.
title_label
.
setWordWrap
(
True
)
# Subtitle label - ENHANCED VISIBILITY
self
.
subtitle_label
=
QLabel
()
self
.
subtitle_label
.
setStyleSheet
(
"""
QLabel {
color: #ffffff;
font-size: 18px;
background: rgba(0,0,0,0.7);
padding: 10px;
border-radius: 5px;
text-shadow: 1px 1px 2px rgba(0,0,0,1.0);
}
"""
)
self
.
subtitle_label
.
setAlignment
(
Qt
.
AlignmentFlag
.
AlignCenter
)
# Time display - ENHANCED VISIBILITY
self
.
time_label
=
QLabel
()
self
.
time_label
.
setStyleSheet
(
"""
QLabel {
color: yellow;
font-size: 16px;
font-weight: bold;
background: rgba(0,0,0,0.8);
padding: 8px;
border-radius: 5px;
border: 1px solid yellow;
}
"""
)
# Ticker label - ENHANCED VISIBILITY
self
.
ticker_label
=
QLabel
()
self
.
ticker_label
.
setStyleSheet
(
"""
QLabel {
color: white;
font-size: 14px;
font-weight: bold;
background: rgba(220, 53, 69, 0.95);
padding: 12px;
border-radius: 5px;
border: 2px solid rgba(255,255,255,0.3);
}
"""
)
self
.
ticker_label
.
setWordWrap
(
True
)
# Layout
layout
=
QVBoxLayout
(
self
)
layout
.
setContentsMargins
(
20
,
20
,
20
,
20
)
# Top section
layout
.
addStretch
(
1
)
layout
.
addWidget
(
self
.
title_label
)
layout
.
addWidget
(
self
.
subtitle_label
)
layout
.
addStretch
(
2
)
# Bottom section
layout
.
addWidget
(
self
.
ticker_label
)
layout
.
addSpacing
(
10
)
# Position time label in top-right corner
self
.
time_label
.
setParent
(
self
)
# Update display with initial data
self
.
update_display
()
logger
.
debug
(
"NativeOverlayWidget UI setup completed"
)
def
resizeEvent
(
self
,
event
):
"""Handle resize events"""
super
()
.
resizeEvent
(
event
)
# Position time label in top-right corner
if
hasattr
(
self
,
'time_label'
)
and
self
.
time_label
:
self
.
time_label
.
adjustSize
()
self
.
time_label
.
move
(
self
.
width
()
-
self
.
time_label
.
width
()
-
20
,
20
)
def
update_overlay_data
(
self
,
data
:
Dict
[
str
,
Any
]):
"""Update overlay display with new data"""
try
:
updated
=
False
if
'title'
in
data
:
self
.
overlay_data
[
'title'
]
=
data
[
'title'
]
updated
=
True
if
'subtitle'
in
data
:
self
.
overlay_data
[
'subtitle'
]
=
data
[
'subtitle'
]
updated
=
True
if
'ticker'
in
data
:
self
.
overlay_data
[
'ticker'
]
=
data
[
'ticker'
]
updated
=
True
if
'currentTime'
in
data
:
self
.
overlay_data
[
'currentTime'
]
=
data
[
'currentTime'
]
updated
=
True
if
updated
:
self
.
update_display
()
logger
.
debug
(
f
"Native overlay updated: {data}"
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to update native overlay: {e}"
)
def
update_display
(
self
):
"""Update all display elements"""
try
:
self
.
title_label
.
setText
(
self
.
overlay_data
.
get
(
'title'
,
''
))
self
.
subtitle_label
.
setText
(
self
.
overlay_data
.
get
(
'subtitle'
,
''
))
self
.
ticker_label
.
setText
(
self
.
overlay_data
.
get
(
'ticker'
,
''
))
self
.
time_label
.
setText
(
self
.
overlay_data
.
get
(
'currentTime'
,
''
))
# Adjust time label position
self
.
time_label
.
adjustSize
()
if
self
.
width
()
>
0
:
self
.
time_label
.
move
(
self
.
width
()
-
self
.
time_label
.
width
()
-
20
,
20
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to update native overlay display: {e}"
)
def
update_position
(
self
,
position
:
float
,
duration
:
float
):
"""Update playback position (compatible with WebEngine interface)"""
try
:
current_time
=
self
.
format_time
(
position
)
total_time
=
self
.
format_time
(
duration
)
self
.
update_overlay_data
({
'currentTime'
:
f
"{current_time} / {total_time}"
})
except
Exception
as
e
:
logger
.
error
(
f
"Failed to update position in native overlay: {e}"
)
def
update_video_info
(
self
,
info
:
Dict
[
str
,
Any
]):
"""Update video information (compatible with WebEngine interface)"""
# Native overlay doesn't show detailed video stats by default
# This is for compatibility with the WebEngine interface
pass
def
format_time
(
self
,
seconds
:
float
)
->
str
:
"""Format time in seconds to MM:SS"""
mins
=
int
(
seconds
//
60
)
secs
=
int
(
seconds
%
60
)
return
f
"{mins:02d}:{secs:02d}"
class
VideoWidget
(
QWidget
):
class
VideoWidget
(
QWidget
):
"""
Composite video widget with selectable overlay type
"""
"""
Video widget for Qt multimedia player
"""
def
__init__
(
self
,
parent
=
None
,
use_native_overlay
=
False
):
def
__init__
(
self
,
parent
=
None
):
super
()
.
__init__
(
parent
)
super
()
.
__init__
(
parent
)
self
.
use_native_overlay
=
use_native_overlay
self
.
setup_ui
()
self
.
setup_ui
()
logger
.
info
(
f
"VideoWidget initialized (native_overlay={use_native_overlay})
"
)
logger
.
info
(
"VideoWidget initialized
"
)
def
setup_ui
(
self
):
def
setup_ui
(
self
):
"""Setup video player - ONLY video widget, overlay handled by PlayerWindow"""
"""Setup video player - ONLY video widget, overlay handled by PlayerWindow"""
...
@@ -1989,11 +2020,10 @@ class PlayerWindow(QMainWindow):
...
@@ -1989,11 +2020,10 @@ class PlayerWindow(QMainWindow):
layout
.
setSpacing
(
0
)
layout
.
setSpacing
(
0
)
# SIMPLE VIDEO WIDGET ONLY - overlay as separate top-level window
# SIMPLE VIDEO WIDGET ONLY - overlay as separate top-level window
overlay_type
=
"Native Qt Widgets"
if
self
.
settings
.
use_native_overlay
else
"QWebEngineView"
logger
.
info
(
"PlayerWindow: Using QWebEngineView overlay"
)
logger
.
info
(
f
"PlayerWindow: Overlay configuration - use_native_overlay={self.settings.use_native_overlay}, using {overlay_type}"
)
# Create simple video widget without any overlay (VideoWidget no longer handles overlays)
# Create simple video widget without any overlay (VideoWidget no longer handles overlays)
self
.
video_widget
=
VideoWidget
(
parent
=
central_widget
,
use_native_overlay
=
False
)
self
.
video_widget
=
VideoWidget
(
parent
=
central_widget
)
layout
.
addWidget
(
self
.
video_widget
,
1
)
layout
.
addWidget
(
self
.
video_widget
,
1
)
# THREADING FIXED: Re-enable overlay system with proper Qt main thread architecture
# THREADING FIXED: Re-enable overlay system with proper Qt main thread architecture
...
@@ -2023,18 +2053,13 @@ class PlayerWindow(QMainWindow):
...
@@ -2023,18 +2053,13 @@ class PlayerWindow(QMainWindow):
self
.
overlay_window
.
setStyleSheet
(
"background: transparent;"
)
self
.
overlay_window
.
setStyleSheet
(
"background: transparent;"
)
logger
.
info
(
"Hardware overlay window configured with native transparency (WA_TranslucentBackground=True)"
)
logger
.
info
(
"Hardware overlay window configured with native transparency (WA_TranslucentBackground=True)"
)
# Create overlay based on configuration - matching test_video_debug.py behavior
# Create overlay using QWebEngineView
if
self
.
settings
.
use_native_overlay
:
debug_overlay
=
getattr
(
self
,
'debug_overlay'
,
False
)
self
.
window_overlay
=
NativeOverlayWidget
(
self
.
overlay_window
)
web_server_url
=
self
.
_get_web_server_base_url
()
logger
.
debug
(
"PlayerWindow: Created NativeOverlayWidget overlay as separate window"
)
# Get database manager from message bus
else
:
db_manager
=
self
.
_get_database_manager
()
# Pass debug_overlay setting and web server URL to OverlayWebView
self
.
window_overlay
=
OverlayWebView
(
self
.
overlay_window
,
debug_overlay
=
debug_overlay
,
web_server_url
=
web_server_url
,
db_manager
=
db_manager
)
debug_overlay
=
getattr
(
self
,
'debug_overlay'
,
False
)
logger
.
debug
(
"PlayerWindow: Created QWebEngineView overlay as separate window"
)
web_server_url
=
self
.
_get_web_server_base_url
()
# Get database manager from message bus
db_manager
=
self
.
_get_database_manager
()
self
.
window_overlay
=
OverlayWebView
(
self
.
overlay_window
,
debug_overlay
=
debug_overlay
,
web_server_url
=
web_server_url
,
db_manager
=
db_manager
)
logger
.
debug
(
"PlayerWindow: Created QWebEngineView overlay as separate window"
)
# Layout for overlay window
# Layout for overlay window
overlay_layout
=
QVBoxLayout
(
self
.
overlay_window
)
overlay_layout
=
QVBoxLayout
(
self
.
overlay_window
)
...
@@ -2862,7 +2887,7 @@ class PlayerWindow(QMainWindow):
...
@@ -2862,7 +2887,7 @@ class PlayerWindow(QMainWindow):
def
_is_native_overlay
(
self
,
overlay_view
):
def
_is_native_overlay
(
self
,
overlay_view
):
"""Check if this is a native overlay"""
"""Check if this is a native overlay"""
return
isinstance
(
overlay_view
,
NativeOverlayWidget
)
return
False
# Native overlay removed
def
_clean_overlay_data
(
self
,
data
:
Dict
[
str
,
Any
])
->
Dict
[
str
,
Any
]:
def
_clean_overlay_data
(
self
,
data
:
Dict
[
str
,
Any
])
->
Dict
[
str
,
Any
]:
"""Clean overlay data by removing null/undefined values"""
"""Clean overlay data by removing null/undefined values"""
...
@@ -3682,8 +3707,7 @@ class QtVideoPlayer(QObject):
...
@@ -3682,8 +3707,7 @@ class QtVideoPlayer(QObject):
return
return
# Log current overlay configuration
# Log current overlay configuration
overlay_type
=
"Native Qt Widgets"
if
self
.
settings
.
use_native_overlay
else
"QWebEngineView"
logger
.
info
(
"Qt Player overlay configuration: QWebEngineView"
)
logger
.
info
(
f
"Qt Player overlay configuration: {overlay_type}"
)
# PERFECT: Now running directly on Qt main thread - no threading issues!
# PERFECT: Now running directly on Qt main thread - no threading issues!
logger
.
info
(
f
"Handler thread: {threading.current_thread().name}"
)
logger
.
info
(
f
"Handler thread: {threading.current_thread().name}"
)
...
...
mbetterclient/qt_player/template_watcher.py
View file @
a596debf
...
@@ -169,18 +169,18 @@ class TemplateWatcher(ThreadedComponent):
...
@@ -169,18 +169,18 @@ class TemplateWatcher(ThreadedComponent):
self
.
_scan_existing_templates
()
self
.
_scan_existing_templates
()
# Message processing loop
# Message processing loop
while
self
.
running
:
while
self
.
running
and
not
self
.
shutdown_event
.
is_set
()
:
try
:
try
:
# Process messages
# Process messages
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
if
message
:
if
message
:
self
.
_process_message
(
message
)
self
.
_process_message
(
message
)
# Update heartbeat
# Update heartbeat
self
.
heartbeat
()
self
.
heartbeat
()
time
.
sleep
(
0.1
)
time
.
sleep
(
0.1
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"TemplateWatcher run loop error: {e}"
)
logger
.
error
(
f
"TemplateWatcher run loop error: {e}"
)
time
.
sleep
(
1.0
)
time
.
sleep
(
1.0
)
...
@@ -199,14 +199,23 @@ class TemplateWatcher(ThreadedComponent):
...
@@ -199,14 +199,23 @@ class TemplateWatcher(ThreadedComponent):
"""Shutdown template watcher"""
"""Shutdown template watcher"""
try
:
try
:
logger
.
info
(
"Shutting down TemplateWatcher..."
)
logger
.
info
(
"Shutting down TemplateWatcher..."
)
if
self
.
observer
:
if
self
.
observer
:
# Unschedule all handlers
for
handler
in
self
.
event_handlers
:
try
:
self
.
observer
.
unschedule
(
handler
)
except
Exception
:
pass
self
.
event_handlers
.
clear
()
self
.
observer
.
stop
()
self
.
observer
.
stop
()
self
.
observer
.
join
()
try
:
self
.
observer
.
join
(
timeout
=
2.0
)
except
Exception
:
pass
self
.
observer
=
None
self
.
observer
=
None
self
.
event_handler
=
None
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"TemplateWatcher shutdown error: {e}"
)
logger
.
error
(
f
"TemplateWatcher shutdown error: {e}"
)
...
...
mbetterclient/web_dashboard/app.py
View file @
a596debf
...
@@ -432,25 +432,25 @@ class WebDashboard(ThreadedComponent):
...
@@ -432,25 +432,25 @@ class WebDashboard(ThreadedComponent):
server_thread
.
start
()
server_thread
.
start
()
# Message processing loop
# Message processing loop
while
self
.
running
:
while
self
.
running
and
not
self
.
shutdown_event
.
is_set
()
:
try
:
try
:
# Process messages
# Process messages
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
message
=
self
.
message_bus
.
get_message
(
self
.
name
,
timeout
=
1.0
)
if
message
:
if
message
:
self
.
_process_message
(
message
)
self
.
_process_message
(
message
)
# Update heartbeat
# Update heartbeat
self
.
heartbeat
()
self
.
heartbeat
()
time
.
sleep
(
0.1
)
time
.
sleep
(
0.1
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"WebDashboard run loop error: {e}"
)
logger
.
error
(
f
"WebDashboard run loop error: {e}"
)
time
.
sleep
(
1.0
)
time
.
sleep
(
1.0
)
# Wait for server thread
# Wait for server thread
to finish (with timeout since it's daemon)
if
server_thread
.
is_alive
():
if
server_thread
and
server_thread
.
is_alive
():
server_thread
.
join
(
timeout
=
5
.0
)
server_thread
.
join
(
timeout
=
2
.0
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
f
"WebDashboard run failed: {e}"
)
logger
.
error
(
f
"WebDashboard run failed: {e}"
)
...
@@ -527,7 +527,8 @@ class WebDashboard(ThreadedComponent):
...
@@ -527,7 +527,8 @@ class WebDashboard(ThreadedComponent):
if
self
.
server
:
if
self
.
server
:
logger
.
info
(
"Shutting down HTTP server..."
)
logger
.
info
(
"Shutting down HTTP server..."
)
self
.
server
.
shutdown
()
self
.
server
.
shutdown
()
logger
.
info
(
"HTTP server shutdown initiated"
)
self
.
server
.
server_close
()
logger
.
info
(
"HTTP server shutdown completed"
)
# Note: SocketIO connections will be closed when the server shuts down
# Note: SocketIO connections will be closed when the server shuts down
# No explicit SocketIO shutdown needed as it's handled by the WSGI server
# No explicit SocketIO shutdown needed as it's handled by the WSGI server
...
...
mbetterclient/web_dashboard/static/overlay.js
View file @
a596debf
...
@@ -3,20 +3,106 @@ class OverlayManager {
...
@@ -3,20 +3,106 @@ class OverlayManager {
this
.
overlayData
=
{};
this
.
overlayData
=
{};
this
.
pendingUpdates
=
[];
this
.
pendingUpdates
=
[];
this
.
webChannelReady
=
false
;
this
.
webChannelReady
=
false
;
// Detect VirtualBox environment
this
.
isVirtualBox
=
this
.
detectVirtualBox
();
if
(
this
.
isVirtualBox
)
{
console
.
warn
(
'VIRTUALBOX DETECTED: Overlay may have compatibility issues'
);
console
.
warn
(
'Common VirtualBox overlay problems:'
);
console
.
warn
(
'1. GPU acceleration disabled'
);
console
.
warn
(
'2. WebGL not available'
);
console
.
warn
(
'3. Qt WebEngine software rendering fallback'
);
console
.
warn
(
'4. Transparency effects may fail'
);
console
.
warn
(
'5. WebChannel communication may be unreliable'
);
}
this
.
initWebChannel
();
this
.
initWebChannel
();
this
.
initCanvas
();
this
.
initCanvas
();
}
}
detectVirtualBox
()
{
// Check for VirtualBox-specific indicators
try
{
// Check user agent for VirtualBox
if
(
navigator
.
userAgent
&&
navigator
.
userAgent
.
includes
(
'VirtualBox'
))
{
return
true
;
}
// Check for VirtualBox-specific plugins or extensions
if
(
navigator
.
plugins
)
{
for
(
let
i
=
0
;
i
<
navigator
.
plugins
.
length
;
i
++
)
{
if
(
navigator
.
plugins
[
i
].
name
&&
navigator
.
plugins
[
i
].
name
.
includes
(
'VirtualBox'
))
{
return
true
;
}
}
}
// Check for VirtualBox guest additions in renderer info
const
canvas
=
document
.
createElement
(
'canvas'
);
const
gl
=
canvas
.
getContext
(
'webgl'
)
||
canvas
.
getContext
(
'experimental-webgl'
);
if
(
gl
)
{
const
renderer
=
gl
.
getParameter
(
gl
.
RENDERER
);
if
(
renderer
&&
(
renderer
.
includes
(
'VirtualBox'
)
||
renderer
.
includes
(
'VBox'
)))
{
return
true
;
}
}
// Check for Mesa software rendering (common in VirtualBox)
if
(
gl
)
{
const
vendor
=
gl
.
getParameter
(
gl
.
VENDOR
);
const
renderer
=
gl
.
getParameter
(
gl
.
RENDERER
);
if
((
vendor
&&
vendor
.
includes
(
'Mesa'
))
||
(
renderer
&&
(
renderer
.
includes
(
'Software'
)
||
renderer
.
includes
(
'Mesa'
))))
{
console
.
warn
(
'MESA SOFTWARE RENDERING DETECTED - likely VirtualBox environment'
);
return
true
;
}
}
return
false
;
}
catch
(
e
)
{
console
.
debug
(
'VirtualBox detection failed:'
,
e
);
return
false
;
}
}
initWebChannel
()
{
initWebChannel
()
{
try
{
try
{
console
.
log
(
'OVERLAY: Starting WebChannel initialization'
);
console
.
log
(
'OVERLAY: VirtualBox detected:'
,
this
.
isVirtualBox
);
if
(
this
.
isVirtualBox
)
{
console
.
warn
(
'VIRTUALBOX: WebChannel initialization - potential communication issues'
);
}
// Log browser/environment information
console
.
log
(
'OVERLAY: Browser environment check:'
);
console
.
log
(
' - User agent:'
,
navigator
.
userAgent
);
console
.
log
(
' - Platform:'
,
navigator
.
platform
);
console
.
log
(
' - Cookie enabled:'
,
navigator
.
cookieEnabled
);
console
.
log
(
' - OnLine:'
,
navigator
.
onLine
);
console
.
log
(
' - Qt object available:'
,
typeof
Qt
!==
'undefined'
);
console
.
log
(
' - qt object available:'
,
typeof
qt
!==
'undefined'
);
console
.
log
(
' - QWebChannel available:'
,
typeof
QWebChannel
!==
'undefined'
);
// Wait for DOM to be fully loaded
// Wait for DOM to be fully loaded
this
.
waitForFullInitialization
(()
=>
{
this
.
waitForFullInitialization
(()
=>
{
console
.
log
(
'OVERLAY: DOM fully loaded, checking Qt WebChannel transport'
);
// Check if Qt WebChannel transport is available (set by QWebEnginePage.setWebChannel)
// Check if Qt WebChannel transport is available (set by QWebEnginePage.setWebChannel)
if
(
typeof
qt
!==
'undefined'
&&
qt
.
webChannelTransport
)
{
if
(
typeof
qt
!==
'undefined'
&&
qt
.
webChannelTransport
)
{
console
.
log
(
'OVERLAY: Qt WebChannel transport available, initializing channel'
);
// Use the transport provided by Qt WebEngine
// Use the transport provided by Qt WebEngine
new
QWebChannel
(
qt
.
webChannelTransport
,
(
channel
)
=>
{
new
QWebChannel
(
qt
.
webChannelTransport
,
(
channel
)
=>
{
console
.
log
(
'OVERLAY: QWebChannel created successfully'
);
// Connect to overlay object
// Connect to overlay object
window
.
overlay
=
channel
.
objects
.
overlay
;
window
.
overlay
=
channel
.
objects
.
overlay
;
console
.
log
(
'OVERLAY: Connected to overlay object:'
,
!!
window
.
overlay
);
if
(
this
.
isVirtualBox
)
{
console
.
warn
(
'VIRTUALBOX: WebChannel connected - testing communication reliability'
);
}
// Flush any buffered console messages
// Flush any buffered console messages
if
(
window
.
flushConsoleBuffer
)
{
if
(
window
.
flushConsoleBuffer
)
{
...
@@ -25,94 +111,128 @@ class OverlayManager {
...
@@ -25,94 +111,128 @@ class OverlayManager {
// Connect signals if overlay object exists
// Connect signals if overlay object exists
if
(
window
.
overlay
)
{
if
(
window
.
overlay
)
{
console
.
log
(
'OVERLAY: Setting up signal connections'
);
// Connect positionChanged signal
// Connect positionChanged signal
if
(
window
.
overlay
.
positionChanged
)
{
if
(
window
.
overlay
.
positionChanged
)
{
window
.
overlay
.
positionChanged
.
connect
((
position
,
duration
)
=>
{
window
.
overlay
.
positionChanged
.
connect
((
position
,
duration
)
=>
{
console
.
log
(
'OVERLAY: positionChanged signal received:'
,
position
,
duration
);
if
(
position
!==
null
&&
duration
!==
null
)
{
if
(
position
!==
null
&&
duration
!==
null
)
{
this
.
updateProgress
(
position
,
duration
);
this
.
updateProgress
(
position
,
duration
);
}
else
{
}
else
{
console
.
warn
(
'positionChanged signal received null/undefined parameters, skipping'
);
console
.
warn
(
'positionChanged signal received null/undefined parameters, skipping'
);
}
}
});
});
console
.
log
(
'OVERLAY: positionChanged signal connected'
);
}
else
{
console
.
warn
(
'OVERLAY: positionChanged signal not available'
);
}
}
// Connect videoInfoChanged signal
// Connect videoInfoChanged signal
if
(
window
.
overlay
.
videoInfoChanged
)
{
if
(
window
.
overlay
.
videoInfoChanged
)
{
window
.
overlay
.
videoInfoChanged
.
connect
((
info
)
=>
{
window
.
overlay
.
videoInfoChanged
.
connect
((
info
)
=>
{
console
.
log
(
'OVERLAY: videoInfoChanged signal received:'
,
info
);
if
(
info
&&
typeof
info
===
'object'
)
{
if
(
info
&&
typeof
info
===
'object'
)
{
this
.
updateVideoInfo
(
info
);
this
.
updateVideoInfo
(
info
);
}
else
{
}
else
{
console
.
warn
(
'videoInfoChanged signal received null/undefined parameter, skipping'
);
console
.
warn
(
'videoInfoChanged signal received null/undefined parameter, skipping'
);
}
}
});
});
console
.
log
(
'OVERLAY: videoInfoChanged signal connected'
);
}
else
{
console
.
warn
(
'OVERLAY: videoInfoChanged signal not available'
);
}
}
// Connect dataUpdated signal for templates that need it (like results.html)
// Connect dataUpdated signal for templates that need it (like results.html)
if
(
window
.
overlay
.
dataUpdated
)
{
if
(
window
.
overlay
.
dataUpdated
)
{
window
.
overlay
.
dataUpdated
.
connect
((
data
)
=>
{
window
.
overlay
.
dataUpdated
.
connect
((
data
)
=>
{
console
.
log
(
'OVERLAY: dataUpdated signal received:'
,
data
);
if
(
data
!==
null
&&
data
!==
undefined
)
{
if
(
data
!==
null
&&
data
!==
undefined
)
{
// Call a global callback if it exists (for results.html and other templates)
// Call a global callback if it exists (for results.html and other templates)
if
(
window
.
onDataUpdated
)
{
if
(
window
.
onDataUpdated
)
{
window
.
onDataUpdated
(
data
);
window
.
onDataUpdated
(
data
);
}
}
console
.
log
(
'dataUpdated signal
received:'
,
data
);
console
.
log
(
'dataUpdated signal
processed'
);
}
else
{
}
else
{
console
.
warn
(
'dataUpdated signal received null/undefined data'
);
console
.
warn
(
'dataUpdated signal received null/undefined data'
);
}
}
});
});
console
.
log
(
'OVERLAY: dataUpdated signal connected'
);
}
else
{
console
.
warn
(
'OVERLAY: dataUpdated signal not available'
);
}
}
// Test WebChannel communication
this
.
testWebChannelCommunication
();
// Mark WebChannel as ready
// Mark WebChannel as ready
this
.
webChannelReady
=
true
;
this
.
webChannelReady
=
true
;
console
.
log
(
'OVERLAY: WebChannel fully initialized and ready'
);
// 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'
);
}
else
{
}
else
{
console
.
warn
(
'Overlay object not found in WebChannel'
);
console
.
error
(
'OVERLAY: Overlay object not found in WebChannel'
);
if
(
this
.
isVirtualBox
)
{
console
.
error
(
'VIRTUALBOX: WebChannel object missing - communication setup failed'
);
}
}
}
});
});
}
else
{
}
else
{
console
.
warn
(
'Qt WebChannel transport not available, falling back to QtWebChannel constructor'
);
console
.
warn
(
'OVERLAY: Qt WebChannel transport not available, falling back to QtWebChannel constructor'
);
if
(
this
.
isVirtualBox
)
{
console
.
warn
(
'VIRTUALBOX: Primary WebChannel transport failed, trying fallback'
);
}
// Fallback: try the old method
// Fallback: try the old method
if
(
typeof
Qt
===
'object'
&&
typeof
QtWebChannel
===
'function'
)
{
if
(
typeof
Qt
===
'object'
&&
typeof
QtWebChannel
===
'function'
)
{
console
.
log
(
'OVERLAY: Using QtWebChannel fallback method'
);
const
channel
=
new
QtWebChannel
();
const
channel
=
new
QtWebChannel
();
channel
.
connectTo
(
'overlay'
,
(
overlay
)
=>
{
channel
.
connectTo
(
'overlay'
,
(
overlay
)
=>
{
console
.
log
(
'OVERLAY: Fallback WebChannel connected'
);
window
.
overlay
=
overlay
;
window
.
overlay
=
overlay
;
// Connect positionChanged signal
// Connect signals (same as above)
overlay
.
positionChanged
.
connect
((
position
,
duration
)
=>
{
if
(
overlay
.
positionChanged
)
{
if
(
position
!==
null
&&
duration
!==
null
)
{
overlay
.
positionChanged
.
connect
((
position
,
duration
)
=>
{
this
.
updateProgress
(
position
,
duration
);
console
.
log
(
'OVERLAY: [FALLBACK] positionChanged signal received:'
,
position
,
duration
);
}
else
{
if
(
position
!==
null
&&
duration
!==
null
)
{
console
.
warn
(
'positionChanged signal received null/undefined parameters, skipping'
);
this
.
updateProgress
(
position
,
duration
);
}
}
else
{
});
console
.
warn
(
'positionChanged signal received null/undefined parameters, skipping'
);
}
});
}
// Connect videoInfoChanged signal
if
(
overlay
.
videoInfoChanged
)
{
overlay
.
videoInfoChanged
.
connect
((
info
)
=>
{
overlay
.
videoInfoChanged
.
connect
((
info
)
=>
{
if
(
info
&&
typeof
info
===
'object'
)
{
console
.
log
(
'OVERLAY: [FALLBACK] videoInfoChanged signal received:'
,
info
);
this
.
updateVideoInfo
(
info
);
if
(
info
&&
typeof
info
===
'object'
)
{
}
else
{
this
.
updateVideoInfo
(
info
);
console
.
warn
(
'videoInfoChanged signal received null/undefined parameter, skipping'
);
}
else
{
}
console
.
warn
(
'videoInfoChanged signal received null/undefined parameter, skipping'
);
});
}
});
}
// Connect dataUpdated signal for templates that need it (like results.html)
if
(
overlay
.
dataUpdated
)
{
if
(
overlay
.
dataUpdated
)
{
overlay
.
dataUpdated
.
connect
((
data
)
=>
{
overlay
.
dataUpdated
.
connect
((
data
)
=>
{
console
.
log
(
'OVERLAY: [FALLBACK] dataUpdated signal received:'
,
data
);
if
(
data
!==
null
&&
data
!==
undefined
)
{
if
(
data
!==
null
&&
data
!==
undefined
)
{
// Call a global callback if it exists (for results.html and other templates)
if
(
window
.
onDataUpdated
)
{
if
(
window
.
onDataUpdated
)
{
window
.
onDataUpdated
(
data
);
window
.
onDataUpdated
(
data
);
}
}
console
.
log
(
'dataUpdated signal
received:'
,
data
);
console
.
log
(
'dataUpdated signal
processed'
);
}
else
{
}
else
{
console
.
warn
(
'dataUpdated signal received null/undefined data'
);
console
.
warn
(
'dataUpdated signal received null/undefined data'
);
}
}
});
});
}
}
// Test WebChannel communication
this
.
testWebChannelCommunication
();
// Mark WebChannel as ready
// Mark WebChannel as ready
this
.
webChannelReady
=
true
;
this
.
webChannelReady
=
true
;
...
@@ -121,19 +241,54 @@ class OverlayManager {
...
@@ -121,19 +241,54 @@ class OverlayManager {
console
.
log
(
'WebChannel connected via fallback method'
);
console
.
log
(
'WebChannel connected via fallback method'
);
});
});
}
else
{
}
else
{
console
.
warn
(
'QtWebChannel not available either'
);
console
.
error
(
'OVERLAY: QtWebChannel not available either'
);
if
(
this
.
isVirtualBox
)
{
console
.
error
(
'VIRTUALBOX: Both WebChannel methods failed - overlays will not work'
);
}
// Retry with exponential backoff
// Retry with exponential backoff
setTimeout
(()
=>
this
.
initWebChannel
(),
1000
);
setTimeout
(()
=>
this
.
initWebChannel
(),
1000
);
}
}
}
}
});
});
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
'WebChannel initialization error:'
,
error
);
console
.
error
(
'OVERLAY: WebChannel initialization error:'
,
error
);
if
(
this
.
isVirtualBox
)
{
console
.
error
(
'VIRTUALBOX: WebChannel initialization failed - overlays disabled'
);
}
// Retry with exponential backoff
// Retry with exponential backoff
setTimeout
(()
=>
this
.
initWebChannel
(),
1000
);
setTimeout
(()
=>
this
.
initWebChannel
(),
1000
);
}
}
}
}
testWebChannelCommunication
()
{
try
{
console
.
log
(
'OVERLAY: Testing WebChannel communication'
);
if
(
window
.
overlay
&&
window
.
overlay
.
log
)
{
// Test basic communication
window
.
overlay
.
log
(
'OVERLAY: WebChannel communication test - basic log message'
);
// Test data retrieval if available
if
(
window
.
overlay
.
getCurrentData
)
{
window
.
overlay
.
getCurrentData
().
then
(
result
=>
{
console
.
log
(
'OVERLAY: WebChannel data retrieval test successful:'
,
result
);
}).
catch
(
error
=>
{
console
.
error
(
'OVERLAY: WebChannel data retrieval test failed:'
,
error
);
if
(
this
.
isVirtualBox
)
{
console
.
error
(
'VIRTUALBOX: WebChannel data retrieval failed - communication unreliable'
);
}
});
}
console
.
log
(
'OVERLAY: WebChannel communication test completed'
);
}
else
{
console
.
error
(
'OVERLAY: Cannot test WebChannel - overlay object or log method missing'
);
}
}
catch
(
error
)
{
console
.
error
(
'OVERLAY: WebChannel communication test failed:'
,
error
);
}
}
waitForFullInitialization
(
callback
)
{
waitForFullInitialization
(
callback
)
{
const
checkReady
=
()
=>
{
const
checkReady
=
()
=>
{
if
(
document
.
readyState
===
'complete'
&&
this
.
validateCriticalElements
())
{
if
(
document
.
readyState
===
'complete'
&&
this
.
validateCriticalElements
())
{
...
@@ -442,15 +597,117 @@ class OverlayManager {
...
@@ -442,15 +597,117 @@ class OverlayManager {
}
}
initCanvas
()
{
initCanvas
()
{
console
.
log
(
'OVERLAY: Initializing canvas for diagnostics'
);
this
.
canvas
=
document
.
getElementById
(
'canvasOverlay'
);
this
.
canvas
=
document
.
getElementById
(
'canvasOverlay'
);
this
.
ctx
=
this
.
canvas
?
this
.
canvas
.
getContext
(
'2d'
)
:
null
;
this
.
ctx
=
this
.
canvas
?
this
.
canvas
.
getContext
(
'2d'
)
:
null
;
this
.
animationFrame
=
null
;
this
.
animationFrame
=
null
;
console
.
log
(
'OVERLAY: Canvas element found:'
,
!!
this
.
canvas
);
console
.
log
(
'OVERLAY: Canvas 2D context available:'
,
!!
this
.
ctx
);
if
(
this
.
isVirtualBox
)
{
console
.
warn
(
'VIRTUALBOX: Canvas initialization - checking WebGL support'
);
this
.
checkWebGLSupport
();
}
// Initialize canvas dimensions
// Initialize canvas dimensions
this
.
resizeCanvas
();
this
.
resizeCanvas
();
// Setup resize listener
// Setup resize listener
window
.
addEventListener
(
'resize'
,
()
=>
this
.
resizeCanvas
());
window
.
addEventListener
(
'resize'
,
()
=>
this
.
resizeCanvas
());
// Run rendering diagnostics
this
.
runRenderingDiagnostics
();
}
checkWebGLSupport
()
{
try
{
console
.
log
(
'VIRTUALBOX: Checking WebGL support for overlay rendering'
);
const
canvas
=
document
.
createElement
(
'canvas'
);
const
gl
=
canvas
.
getContext
(
'webgl'
)
||
canvas
.
getContext
(
'experimental-webgl'
);
console
.
log
(
'VIRTUALBOX: WebGL context available:'
,
!!
gl
);
if
(
gl
)
{
const
debugInfo
=
gl
.
getExtension
(
'WEBGL_debug_renderer_info'
);
if
(
debugInfo
)
{
const
renderer
=
gl
.
getParameter
(
debugInfo
.
UNMASKED_RENDERER_WEBGL
);
const
vendor
=
gl
.
getParameter
(
debugInfo
.
UNMASKED_VENDOR_WEBGL
);
console
.
log
(
'VIRTUALBOX: WebGL renderer:'
,
renderer
);
console
.
log
(
'VIRTUALBOX: WebGL vendor:'
,
vendor
);
// Check for software rendering indicators
if
(
renderer
&&
(
renderer
.
includes
(
'Software'
)
||
renderer
.
includes
(
'Mesa'
)
||
renderer
.
includes
(
'LLVM'
)))
{
console
.
warn
(
'VIRTUALBOX: SOFTWARE WEBGL RENDERING DETECTED - overlays may not display properly'
);
console
.
warn
(
'VIRTUALBOX: This is the likely cause of invisible overlays'
);
}
}
else
{
console
.
log
(
'VIRTUALBOX: WebGL debug info not available'
);
}
// Test basic WebGL functionality
const
vertexShader
=
gl
.
createShader
(
gl
.
VERTEX_SHADER
);
const
fragmentShader
=
gl
.
createShader
(
gl
.
FRAGMENT_SHADER
);
console
.
log
(
'VIRTUALBOX: WebGL shader creation works:'
,
!!
(
vertexShader
&&
fragmentShader
));
}
else
{
console
.
error
(
'VIRTUALBOX: WebGL not available - overlays will fail to render'
);
console
.
error
(
'VIRTUALBOX: This is the primary cause of overlay visibility issues'
);
}
}
catch
(
error
)
{
console
.
error
(
'VIRTUALBOX: WebGL check failed:'
,
error
);
}
}
runRenderingDiagnostics
()
{
try
{
console
.
log
(
'OVERLAY: Running rendering diagnostics'
);
// Check CSS rendering capabilities
const
testElement
=
document
.
createElement
(
'div'
);
testElement
.
style
.
position
=
'absolute'
;
testElement
.
style
.
left
=
'-9999px'
;
testElement
.
style
.
background
=
'rgba(255, 0, 0, 0.5)'
;
testElement
.
style
.
width
=
'100px'
;
testElement
.
style
.
height
=
'100px'
;
document
.
body
.
appendChild
(
testElement
);
// Test CSS transparency
const
computedStyle
=
window
.
getComputedStyle
(
testElement
);
console
.
log
(
'OVERLAY: CSS rgba() support:'
,
computedStyle
.
background
.
includes
(
'rgba'
));
// Test CSS transforms
testElement
.
style
.
transform
=
'translate3d(0, 0, 0)'
;
const
hasTransform
=
computedStyle
.
transform
&&
computedStyle
.
transform
!==
'none'
;
console
.
log
(
'OVERLAY: CSS 3D transforms support:'
,
hasTransform
);
if
(
this
.
isVirtualBox
&&
!
hasTransform
)
{
console
.
warn
(
'VIRTUALBOX: CSS 3D transforms not supported - overlay positioning may fail'
);
}
// Clean up
document
.
body
.
removeChild
(
testElement
);
// Check for compositor issues
console
.
log
(
'OVERLAY: Window device pixel ratio:'
,
window
.
devicePixelRatio
);
console
.
log
(
'OVERLAY: Screen color depth:'
,
screen
.
colorDepth
);
console
.
log
(
'OVERLAY: Screen pixel depth:'
,
screen
.
pixelDepth
);
if
(
this
.
isVirtualBox
)
{
if
(
window
.
devicePixelRatio
!==
1
)
{
console
.
warn
(
'VIRTUALBOX: Non-standard device pixel ratio detected - may cause rendering issues'
);
}
}
console
.
log
(
'OVERLAY: Rendering diagnostics completed'
);
}
catch
(
error
)
{
console
.
error
(
'OVERLAY: Rendering diagnostics failed:'
,
error
);
}
}
}
resizeCanvas
()
{
resizeCanvas
()
{
...
...
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