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
89aa1260
Commit
89aa1260
authored
Dec 04, 2025
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit latest changes on experimental branch
parent
9ed29d92
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1599 additions
and
3 deletions
+1599
-3
main.py
main.py
+13
-1
settings.py
mbetterclient/config/settings.py
+1
-0
application.py
mbetterclient/core/application.py
+1
-0
player.py
mbetterclient/qt_player/player.py
+567
-2
web_player.html
mbetterclient/qt_player/web_player_assets/web_player.html
+300
-0
web_player.js
mbetterclient/qt_player/web_player_assets/web_player.js
+328
-0
test_simple_video.html
test_simple_video.html
+126
-0
test_video_playback.py
test_video_playback.py
+183
-0
test_web_player.py
test_web_player.py
+80
-0
No files found.
main.py
View file @
89aa1260
...
...
@@ -229,6 +229,12 @@ Examples:
help
=
'Enable single window overlay mode (overlay stacks on top of player widget instead of separate window)'
)
parser
.
add_argument
(
'--overlay-web'
,
action
=
'store_true'
,
help
=
'Enable web-based player mode (QWebEngineView with static HTML/JS player instead of native Qt player)'
)
return
parser
.
parse_args
()
def
validate_arguments
(
args
):
...
...
@@ -241,6 +247,11 @@ def validate_arguments(args):
print
(
"Error: Web port must be between 1 and 65535"
)
sys
.
exit
(
1
)
# Ensure mutual exclusivity between overlay modes
if
args
.
overlay_single
and
args
.
overlay_web
:
print
(
"Error: Cannot use both --overlay-single and --overlay-web options. They are mutually exclusive."
)
sys
.
exit
(
1
)
# Directory creation is handled by AppSettings.ensure_directories()
# which uses persistent user directories for PyInstaller compatibility
pass
...
...
@@ -300,6 +311,7 @@ def main():
# Overlay settings
settings
.
qt
.
overlay_single_window
=
args
.
overlay_single
settings
.
qt
.
overlay_web_player
=
args
.
overlay_web
if
args
.
db_path
:
settings
.
database_path
=
args
.
db_path
...
...
mbetterclient/config/settings.py
View file @
89aa1260
...
...
@@ -229,6 +229,7 @@ class QtConfig:
default_template
:
str
=
"news_template"
overlay_opacity
:
float
=
0.9
overlay_single_window
:
bool
=
False
overlay_web_player
:
bool
=
False
# Performance settings
hardware_acceleration
:
bool
=
True
...
...
mbetterclient/core/application.py
View file @
89aa1260
...
...
@@ -140,6 +140,7 @@ class MbetterClientApplication:
# Preserve command line overlay settings
stored_settings
.
qt
.
overlay_single_window
=
self
.
settings
.
qt
.
overlay_single_window
stored_settings
.
qt
.
overlay_web_player
=
self
.
settings
.
qt
.
overlay_web_player
# Preserve command line SSL settings
stored_settings
.
web
.
enable_ssl
=
self
.
settings
.
web
.
enable_ssl
...
...
mbetterclient/qt_player/player.py
View file @
89aa1260
...
...
@@ -55,6 +55,10 @@ class OverlayWebChannel(QObject):
# Signal to receive console messages from JavaScript
consoleMessage
=
pyqtSignal
(
str
,
str
,
int
,
str
)
# level, message, line, source
# Signals for web player communication
playVideo
=
pyqtSignal
(
str
)
# filePath
updateOverlayData
=
pyqtSignal
(
dict
)
# overlay data
def
__init__
(
self
,
db_manager
=
None
,
message_bus
=
None
):
super
()
.
__init__
()
self
.
mutex
=
QMutex
()
...
...
@@ -245,6 +249,18 @@ class OverlayWebChannel(QObject):
logger
.
error
(
f
"Failed to get timer state: {e}"
)
return
json
.
dumps
({
"running"
:
False
,
"remaining_seconds"
:
0
})
@
pyqtSlot
(
str
)
def
playVideo
(
self
,
filePath
:
str
):
"""Send play video command to JavaScript (called by Qt)"""
logger
.
info
(
f
"OverlayWebChannel.playVideo called with: {filePath}"
)
self
.
playVideo
.
emit
(
filePath
)
@
pyqtSlot
(
dict
)
def
updateOverlayData
(
self
,
data
:
dict
):
"""Send overlay data update to JavaScript (called by Qt)"""
logger
.
debug
(
f
"OverlayWebChannel.updateOverlayData called with: {data}"
)
self
.
updateOverlayData
.
emit
(
data
)
@
pyqtSlot
(
int
,
result
=
str
)
def
getWinningBets
(
self
,
match_id
:
int
)
->
str
:
"""Provide winning bets data for a match to JavaScript via WebChannel"""
...
...
@@ -1907,6 +1923,546 @@ class PlayerControlsWidget(QWidget):
return
f
"{minutes:02d}:{seconds:02d}"
class
WebPlayerWindow
(
QMainWindow
):
"""Web-based player window using QWebEngineView with static HTML/JS"""
# Signals for thread communication
position_changed
=
pyqtSignal
(
int
,
int
)
video_loaded
=
pyqtSignal
(
str
)
def
__init__
(
self
,
settings
:
QtConfig
,
message_bus
:
MessageBus
=
None
,
debug_overlay
:
bool
=
False
,
qt_player
=
None
):
super
()
.
__init__
()
self
.
settings
=
settings
self
.
debug_overlay
=
debug_overlay
self
.
mutex
=
QMutex
()
self
.
thread_pool
=
QThreadPool
()
self
.
thread_pool
.
setMaxThreadCount
(
4
)
self
.
_message_bus
=
message_bus
# Store message bus reference for shutdown messages
self
.
qt_player
=
qt_player
# Reference to parent QtVideoPlayer for message sending
self
.
setup_ui
()
self
.
setup_web_player
()
self
.
setup_timers
()
logger
.
info
(
"WebPlayerWindow initialized"
)
def
setup_ui
(
self
):
"""Setup web player window UI"""
self
.
setWindowTitle
(
"MbetterClient - Web Player"
)
# Clean interface
self
.
setMenuBar
(
None
)
self
.
setStatusBar
(
None
)
self
.
setContextMenuPolicy
(
Qt
.
ContextMenuPolicy
.
NoContextMenu
)
# Set black background
from
PyQt6.QtGui
import
QPalette
palette
=
self
.
palette
()
palette
.
setColor
(
QPalette
.
ColorRole
.
Window
,
QColor
(
0
,
0
,
0
))
palette
.
setColor
(
QPalette
.
ColorRole
.
WindowText
,
QColor
(
255
,
255
,
255
))
palette
.
setColor
(
QPalette
.
ColorRole
.
Base
,
QColor
(
0
,
0
,
0
))
self
.
setPalette
(
palette
)
self
.
setStyleSheet
(
"QMainWindow { background-color: black; }"
)
# Central widget
central_widget
=
QWidget
()
central_widget
.
setAutoFillBackground
(
True
)
central_widget
.
setPalette
(
palette
)
central_widget
.
setStyleSheet
(
"background-color: black;"
)
self
.
setCentralWidget
(
central_widget
)
# Layout
layout
=
QVBoxLayout
(
central_widget
)
layout
.
setContentsMargins
(
0
,
0
,
0
,
0
)
layout
.
setSpacing
(
0
)
# Create QWebEngineView for web player
self
.
web_player_view
=
QWebEngineView
(
central_widget
)
layout
.
addWidget
(
self
.
web_player_view
,
1
)
# Window settings
if
self
.
settings
.
fullscreen
:
self
.
showFullScreen
()
else
:
self
.
resize
(
self
.
settings
.
window_width
,
self
.
settings
.
window_height
)
self
.
show
()
self
.
raise_
()
self
.
activateWindow
()
self
.
repaint
()
if
self
.
settings
.
always_on_top
:
self
.
setWindowFlags
(
self
.
windowFlags
()
|
Qt
.
WindowType
.
WindowStaysOnTopHint
)
self
.
show
()
def
setup_web_player
(
self
):
"""Setup web player with QWebEngineView"""
# Get web player HTML path
web_player_html_path
=
self
.
_get_web_player_html_path
()
if
not
web_player_html_path
or
not
web_player_html_path
.
exists
():
logger
.
error
(
f
"Web player HTML not found: {web_player_html_path}"
)
# Load fallback
self
.
_load_fallback_web_player
()
return
logger
.
info
(
f
"Loading web player from: {web_player_html_path}"
)
# Load the web player HTML
self
.
web_player_view
.
load
(
QUrl
.
fromLocalFile
(
str
(
web_player_html_path
)))
# Setup WebChannel
self
.
_setup_web_player_webchannel
()
# Connect signals
self
.
web_player_view
.
page
()
.
loadFinished
.
connect
(
self
.
_on_web_player_loaded
)
def
_get_web_player_html_path
(
self
)
->
Path
:
"""Get path to web player HTML file"""
try
:
# First try the web_player_assets directory
web_player_dir
=
Path
(
__file__
)
.
parent
/
"web_player_assets"
web_player_html
=
web_player_dir
/
"web_player.html"
if
web_player_html
.
exists
():
return
web_player_html
# Fallback to templates directory (for backwards compatibility)
templates_dir
=
Path
(
__file__
)
.
parent
/
"templates"
fallback_html
=
templates_dir
/
"web_player.html"
if
fallback_html
.
exists
():
return
fallback_html
logger
.
error
(
"Web player HTML not found in any location"
)
return
None
except
Exception
as
e
:
logger
.
error
(
f
"Failed to determine web player HTML path: {e}"
)
return
None
def
_load_fallback_web_player
(
self
):
"""Load fallback web player if main file not found"""
try
:
fallback_html
=
"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Mbetter Web Player - Fallback</title>
<style>
body {
margin: 0;
padding: 0;
background: black;
color: white;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
text-align: center;
}
.error-message {
background: rgba(255, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
max-width: 80
%
;
}
</style>
</head>
<body>
<div class="error-message">
<h1>Web Player Error</h1>
<p>Web player HTML file not found. Please check installation.</p>
</div>
</body>
</html>
"""
self
.
web_player_view
.
setHtml
(
fallback_html
)
logger
.
error
(
"Loaded fallback web player HTML"
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to load fallback web player: {e}"
)
def
_setup_web_player_webchannel
(
self
):
"""Setup WebChannel for web player communication"""
try
:
# Setup WebChannel
self
.
web_channel
=
QWebChannel
()
self
.
web_player_channel
=
OverlayWebChannel
(
db_manager
=
self
.
_get_database_manager
(),
message_bus
=
self
.
_message_bus
)
self
.
web_channel
.
registerObject
(
"overlay"
,
self
.
web_player_channel
)
self
.
web_player_view
.
page
()
.
setWebChannel
(
self
.
web_channel
)
# Connect WebChannel signals to handle video playback and overlay updates
self
.
web_player_channel
.
playVideo
.
connect
(
self
.
_handle_web_player_play_video
)
self
.
web_player_channel
.
updateOverlayData
.
connect
(
self
.
_handle_web_player_update_overlay
)
# Add WebChannel JavaScript to the page
self
.
_inject_webchannel_javascript
()
logger
.
info
(
"WebChannel setup completed for web player"
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to setup WebChannel for web player: {e}"
)
def
_on_web_player_loaded
(
self
,
ok
=
None
):
"""Handle web player load completion"""
try
:
if
ok
is
not
None
and
not
ok
:
logger
.
warning
(
"Web player failed to load"
)
return
logger
.
info
(
"Web player loaded successfully"
)
# Inject WebChannel setup into the page
self
.
_inject_webchannel_setup
()
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle web player load: {e}"
)
def
_inject_webchannel_javascript
(
self
):
"""Inject WebChannel JavaScript into the page"""
try
:
# Inject QWebChannel JavaScript library
webchannel_js
=
"""
// QWebChannel JavaScript library
var QWebChannel = (function() {
// Simplified QWebChannel implementation
function QWebChannel(transport, initCallback) {
this.transport = transport;
this.objects = {};
this.execCallbacks = {};
// Setup transport
if (transport) {
transport.onmessage = this.handleMessage.bind(this);
}
// Call initialization callback
if (initCallback) {
initCallback(this);
}
}
QWebChannel.prototype.handleMessage = function(message) {
try {
var data = JSON.parse(message.data);
if (data.type === 'response') {
if (this.execCallbacks[data.id]) {
this.execCallbacks[data.id](data.data);
delete this.execCallbacks[data.id];
}
} else if (data.type === 'signal') {
var objectName = data.object;
var signalName = data.signal;
var args = data.args;
if (this.objects[objectName] && this.objects[objectName][signalName]) {
this.objects[objectName][signalName].apply(this.objects[objectName], args);
}
}
} catch (e) {
console.error('Error handling WebChannel message:', e);
}
};
QWebChannel.prototype.call = function(objectName, methodName, args, callback) {
var id = Math.random().toString(36).substr(2, 9);
this.execCallbacks[id] = callback;
var message = {
type: 'call',
id: id,
object: objectName,
method: methodName,
args: args
};
this.transport.postMessage(JSON.stringify(message));
};
return QWebChannel;
})();
// Make QWebChannel available globally
window.QWebChannel = QWebChannel;
"""
self
.
web_player_view
.
page
()
.
runJavaScript
(
webchannel_js
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to inject WebChannel JavaScript: {e}"
)
def
_inject_webchannel_setup
(
self
):
"""Inject WebChannel setup into the web player page"""
try
:
# Run JavaScript to setup WebChannel connection
setup_js
=
"""
// Setup WebChannel connection
if (typeof QWebChannel !== 'undefined') {
new QWebChannel(qt.webChannelTransport, function(channel) {
console.log('WebChannel connected in web player');
// Connect to overlay object
if (channel.objects.overlay) {
window.overlay = channel.objects.overlay;
console.log('Web player overlay object connected');
// Set up data update handler
if (window.updateOverlayData) {
window.overlay.dataUpdated.connect(function(data) {
window.updateOverlayData(data);
});
}
// Set up video play handler
if (window.playVideo) {
window.overlay.playVideo.connect(function(filePath) {
console.log('WebChannel playVideo signal received:', filePath);
window.playVideo(filePath);
});
}
// Set up overlay data update handler
if (window.updateOverlayData) {
window.overlay.updateOverlayData.connect(function(data) {
console.log('WebChannel updateOverlayData signal received:', data);
window.updateOverlayData(data);
});
}
}
});
} else {
console.error('QWebChannel not available in web player');
}
"""
self
.
web_player_view
.
page
()
.
runJavaScript
(
setup_js
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to inject WebChannel setup: {e}"
)
def
_handle_web_player_play_video
(
self
,
filePath
:
str
):
"""Handle play video signal from WebChannel"""
try
:
logger
.
info
(
f
"WebPlayerWindow: Handling play video signal: {filePath}"
)
# This method is called when JavaScript signals to play a video
# For now, we can just log it since the video should already be playing
# through the direct JavaScript injection in play_video method
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle web player play video: {e}"
)
def
_handle_web_player_update_overlay
(
self
,
data
:
dict
):
"""Handle overlay data update signal from WebChannel"""
try
:
logger
.
info
(
f
"WebPlayerWindow: Handling overlay update signal: {data}"
)
# This method is called when JavaScript signals to update overlay data
# For now, we can just log it since overlay updates are handled
# through the direct JavaScript injection in play_video method
except
Exception
as
e
:
logger
.
error
(
f
"Failed to handle web player overlay update: {e}"
)
def
_get_database_manager
(
self
):
"""Get database manager from message bus"""
try
:
if
hasattr
(
self
,
'_message_bus'
)
and
self
.
_message_bus
:
# Try to get db_manager from web_dashboard component
try
:
web_dashboard_queue
=
self
.
_message_bus
.
_queues
.
get
(
'web_dashboard'
)
if
web_dashboard_queue
and
hasattr
(
web_dashboard_queue
,
'component'
):
component
=
web_dashboard_queue
.
component
if
hasattr
(
component
,
'db_manager'
):
logger
.
debug
(
"WebPlayerWindow: Got db_manager from web_dashboard component"
)
return
component
.
db_manager
except
Exception
as
e
:
logger
.
debug
(
f
"WebPlayerWindow: Could not get db_manager from message bus: {e}"
)
# Fallback: create database manager directly
from
..config.settings
import
get_user_data_dir
from
..database.manager
import
DatabaseManager
db_path
=
get_user_data_dir
()
/
"mbetterclient.db"
logger
.
debug
(
f
"WebPlayerWindow: Creating database manager directly: {db_path}"
)
db_manager
=
DatabaseManager
(
str
(
db_path
))
if
db_manager
.
initialize
():
return
db_manager
else
:
logger
.
warning
(
"WebPlayerWindow: Failed to initialize database manager"
)
return
None
except
Exception
as
e
:
logger
.
error
(
f
"WebPlayerWindow: Failed to get database manager: {e}"
)
return
None
def
play_video
(
self
,
file_path
:
str
,
template_data
:
Dict
[
str
,
Any
]
=
None
,
template_name
:
str
=
None
,
loop_data
:
Dict
[
str
,
Any
]
=
None
):
"""Play video file in web player"""
try
:
logger
.
info
(
f
"WebPlayerWindow.play_video() called with: {file_path}"
)
if
not
self
.
web_player_view
:
logger
.
error
(
"Web player view not available"
)
return
# Convert file path to URL for web player
file_url
=
QUrl
.
fromLocalFile
(
str
(
file_path
))
.
toString
()
logger
.
info
(
f
"Converted file path to URL: {file_url}"
)
# Send play command to web player via WebChannel
if
hasattr
(
self
,
'web_player_channel'
)
and
self
.
web_player_channel
:
# Update overlay data first
overlay_data
=
template_data
or
{}
overlay_data
.
update
({
'title'
:
f
'Playing: {Path(file_path).name}'
,
'message'
:
'Web Player Active'
,
'videoPath'
:
file_url
})
self
.
web_player_channel
.
updateOverlayData
(
overlay_data
)
self
.
web_player_channel
.
playVideo
(
file_url
)
logger
.
info
(
"Sent video play command to web player"
)
else
:
# Fallback: inject JavaScript to play video directly
play_js
=
f
"""
if (window.webPlayer && window.webPlayer.playVideo) {{
window.webPlayer.playVideo('{file_url}');
}} else {{
console.error('WebPlayer.playVideo function not available in web player');
}}
"""
self
.
web_player_view
.
page
()
.
runJavaScript
(
play_js
)
except
Exception
as
e
:
logger
.
error
(
f
"Failed to play video in web player: {e}"
)
def
setup_timers
(
self
):
"""Setup timers for web player"""
# Overlay update timer
self
.
overlay_timer
=
QTimer
()
self
.
overlay_timer
.
timeout
.
connect
(
self
.
update_overlay_periodically
)
self
.
overlay_timer
.
start
(
1000
)
# Update every second
def
get_media_player_state
(
self
)
->
int
:
"""Get media player state for compatibility"""
return
1
# QMediaPlayer.PlaybackState.PlayingState equivalent
def
get_media_player_position
(
self
)
->
int
:
"""Get media player position for compatibility"""
return
0
# Default position
def
get_media_player_duration
(
self
)
->
int
:
"""Get media player duration for compatibility"""
return
0
# Default duration
@
property
def
media_player
(
self
):
"""Mock media_player property for compatibility"""
# Return a mock object that has the expected properties and methods
class
MockMediaPlayer
:
def
playbackState
(
self
):
return
1
# QMediaPlayer.PlaybackState.PlayingState equivalent
def
position
(
self
):
return
0
# Default position
def
duration
(
self
):
return
0
# Default duration
def
play
(
self
):
pass
def
pause
(
self
):
pass
def
stop
(
self
):
pass
def
setSource
(
self
,
url
):
pass
def
setVideoOutput
(
self
,
widget
):
pass
def
videoOutput
(
self
):
return
None
def
error
(
self
):
return
None
def
source
(
self
):
return
None
return
MockMediaPlayer
()
@
property
def
audio_output
(
self
):
"""Mock audio_output property for compatibility"""
# Return a mock object that has the expected properties and methods
class
MockAudioOutput
:
def
setVolume
(
self
,
volume
):
pass
def
volume
(
self
):
return
1.0
# Default volume
def
setMuted
(
self
,
muted
):
pass
def
isMuted
(
self
):
return
False
return
MockAudioOutput
()
def
update_overlay_periodically
(
self
):
"""Periodic overlay updates for web player"""
try
:
current_time
=
time
.
strftime
(
"
%
H:
%
M:
%
S"
)
if
hasattr
(
self
,
'web_player_channel'
):
time_data
=
{
'currentTime'
:
current_time
,
'webServerBaseUrl'
:
self
.
_get_web_server_base_url
()
}
self
.
web_player_channel
.
send_data_update
(
time_data
)
except
Exception
as
e
:
logger
.
error
(
f
"Periodic overlay update failed: {e}"
)
def
_get_web_server_base_url
(
self
)
->
str
:
"""Get the web server base URL for API requests"""
try
:
# Try to get the URL from the parent QtVideoPlayer component
if
hasattr
(
self
,
'_message_bus'
)
and
self
.
_message_bus
:
# Look for the qt_player component in the message bus
qt_player_queue
=
self
.
_message_bus
.
_queues
.
get
(
'qt_player'
)
if
qt_player_queue
and
hasattr
(
qt_player_queue
,
'component'
):
qt_player
=
qt_player_queue
.
component
if
hasattr
(
qt_player
,
'web_dashboard_url'
)
and
qt_player
.
web_dashboard_url
:
logger
.
debug
(
f
"Web server base URL from QtVideoPlayer: {qt_player.web_dashboard_url}"
)
return
qt_player
.
web_dashboard_url
# Fallback to defaults if not available
base_url
=
"http://127.0.0.1:5001"
logger
.
debug
(
f
"Web server base URL using fallback: {base_url}"
)
return
base_url
except
Exception
as
e
:
logger
.
error
(
f
"Failed to determine web server base URL: {e}"
)
return
"http://127.0.0.1:5001"
def
closeEvent
(
self
,
event
):
"""Handle window close"""
with
QMutexLocker
(
self
.
mutex
):
self
.
overlay_timer
.
stop
()
if
hasattr
(
self
,
'web_player_channel'
):
# Clean up WebChannel
pass
logger
.
info
(
"Web player window closing"
)
event
.
accept
()
class
PlayerWindow
(
QMainWindow
):
"""Enhanced main player window with thread-safe operations"""
...
...
@@ -3163,6 +3719,15 @@ class QtVideoPlayer(QObject):
self
.
_configure_linux_app_settings
()
# Create player window with message bus reference and debug settings
logger
.
info
(
f
"DEBUG: overlay_web_player setting = {self.settings.overlay_web_player}"
)
logger
.
info
(
f
"DEBUG: overlay_single_window setting = {self.settings.overlay_single_window}"
)
if
self
.
settings
.
overlay_web_player
:
# Use WebPlayerWindow for web-based player
logger
.
info
(
"DEBUG: Creating WebPlayerWindow for web-based player"
)
self
.
window
=
WebPlayerWindow
(
self
.
settings
,
self
.
message_bus
,
debug_overlay
=
self
.
debug_overlay
,
qt_player
=
self
)
else
:
# Use standard PlayerWindow
logger
.
info
(
"DEBUG: Creating standard PlayerWindow"
)
self
.
window
=
PlayerWindow
(
self
.
settings
,
self
.
message_bus
,
debug_overlay
=
self
.
debug_overlay
,
qt_player
=
self
)
# CRITICAL: Connect signal to slot for cross-thread video playback
...
...
mbetterclient/qt_player/web_player_assets/web_player.html
0 → 100644
View file @
89aa1260
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
Mbetter Web Player
</title>
<style>
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
}
body
{
font-family
:
'Arial'
,
sans-serif
;
background
:
black
;
overflow
:
hidden
;
width
:
100vw
;
height
:
100vh
;
position
:
relative
;
color
:
white
;
}
/* Video container */
.video-container
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
background
:
black
;
z-index
:
100
;
/* Increased z-index to ensure video is above background but below overlays */
}
/* Video element */
#webVideoPlayer
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
background
:
black
;
visibility
:
visible
!important
;
/* Ensure video is visible */
opacity
:
1
!important
;
/* Ensure video is not transparent */
}
/* Overlay container */
.overlay-container
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
pointer-events
:
none
;
z-index
:
1000
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
/* Message panel */
.message-panel
{
background
:
rgba
(
0
,
123
,
255
,
0.40
);
border-radius
:
20px
;
padding
:
40px
60px
;
min-width
:
500px
;
max-width
:
80%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
box-shadow
:
0
8px
32px
rgba
(
0
,
0
,
0
,
0.3
);
backdrop-filter
:
blur
(
10px
);
border
:
2px
solid
rgba
(
255
,
255
,
255
,
0.2
);
opacity
:
0
;
transform
:
translateY
(
-30px
);
animation
:
slideInDown
1s
ease-out
forwards
;
}
.message-title
{
color
:
#ffffff
;
font-size
:
32px
;
font-weight
:
bold
;
text-align
:
center
;
margin-bottom
:
20px
;
text-shadow
:
3px
3px
6px
rgba
(
0
,
0
,
0
,
0.8
);
opacity
:
0
;
animation
:
titleFadeIn
1.5s
ease-out
0.5s
forwards
;
}
.message-content
{
color
:
rgba
(
255
,
255
,
255
,
0.95
);
font-size
:
20px
;
text-align
:
center
;
line-height
:
1.6
;
max-width
:
100%
;
word-wrap
:
break-word
;
text-shadow
:
2px
2px
4px
rgba
(
0
,
0
,
0
,
0.6
);
opacity
:
0
;
animation
:
contentFadeIn
1.5s
ease-out
1s
forwards
;
}
.message-icon
{
font-size
:
48px
;
color
:
#ffffff
;
margin-bottom
:
20px
;
text-shadow
:
3px
3px
6px
rgba
(
0
,
0
,
0
,
0.8
);
opacity
:
0
;
animation
:
iconBounce
2s
ease-out
0.2s
forwards
;
}
/* Timer in top right */
.timer-container
{
position
:
absolute
;
top
:
20px
;
right
:
20px
;
background
:
rgba
(
0
,
123
,
255
,
0.8
);
color
:
white
;
padding
:
12px
18px
;
border-radius
:
8px
;
font-size
:
32px
;
font-weight
:
bold
;
font-family
:
'Courier New'
,
monospace
;
border
:
2px
solid
rgba
(
255
,
255
,
255
,
0.3
);
z-index
:
1001
;
min-width
:
140px
;
text-align
:
center
;
}
/* Bottom info bar */
.info-bar
{
position
:
absolute
;
bottom
:
0
;
left
:
0
;
right
:
0
;
background
:
rgba
(
0
,
123
,
255
,
0.8
);
backdrop-filter
:
blur
(
10px
);
border-top
:
2px
solid
rgba
(
255
,
255
,
255
,
0.3
);
padding
:
20px
;
z-index
:
1001
;
}
.fighter-names
{
color
:
white
;
font-size
:
36px
;
font-weight
:
bold
;
text-align
:
center
;
margin-bottom
:
8px
;
text-shadow
:
2px
2px
4px
rgba
(
0
,
0
,
0
,
0.8
);
}
.venue-info
{
color
:
rgba
(
255
,
255
,
255
,
0.95
);
font-size
:
28px
;
text-align
:
center
;
font-style
:
italic
;
text-shadow
:
1px
1px
2px
rgba
(
0
,
0
,
0
,
0.8
);
}
/* Animations */
@keyframes
slideInDown
{
0
%
{
opacity
:
0
;
transform
:
translateY
(
-50px
)
scale
(
0.8
);
}
100
%
{
opacity
:
1
;
transform
:
translateY
(
0
)
scale
(
1
);
}
}
@keyframes
titleFadeIn
{
0
%
{
opacity
:
0
;
transform
:
translateY
(
-10px
);
}
100
%
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
@keyframes
contentFadeIn
{
0
%
{
opacity
:
0
;
transform
:
translateY
(
10px
);
}
100
%
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
@keyframes
iconBounce
{
0
%
{
opacity
:
0
;
transform
:
scale
(
0.5
);
}
50
%
{
transform
:
scale
(
1.2
);
}
100
%
{
opacity
:
1
;
transform
:
scale
(
1
);
}
}
/* Responsive Design */
@media
(
max-width
:
1200px
)
{
.message-panel
{
padding
:
30px
50px
;
min-width
:
400px
;
}
.message-title
{
font-size
:
28px
;
}
.message-content
{
font-size
:
18px
;
}
.message-icon
{
font-size
:
40px
;
}
.timer-container
{
font-size
:
28px
;
padding
:
10px
14px
;
min-width
:
130px
;
}
}
@media
(
max-width
:
800px
)
{
.message-panel
{
padding
:
25px
35px
;
min-width
:
90%
;
max-width
:
95%
;
}
.message-title
{
font-size
:
24px
;
margin-bottom
:
15px
;
}
.message-content
{
font-size
:
16px
;
line-height
:
1.5
;
}
.message-icon
{
font-size
:
36px
;
}
.timer-container
{
font-size
:
24px
;
padding
:
8px
12px
;
top
:
10px
;
right
:
10px
;
min-width
:
120px
;
}
.info-bar
{
padding
:
15px
;
}
}
</style>
</head>
<body>
<!-- Video container -->
<div
class=
"video-container"
>
<video
id=
"webVideoPlayer"
controls
autoplay
playsinline
>
<source
src=
""
type=
"video/mp4"
>
Your browser does not support the video tag.
</video>
</div>
<!-- Timer in top right -->
<div
class=
"timer-container"
id=
"matchTimer"
>
00:00
</div>
<!-- Overlay container -->
<div
class=
"overlay-container"
>
<div
class=
"message-panel"
id=
"messagePanel"
>
<div
class=
"message-icon"
id=
"messageIcon"
>
📢
</div>
<div
class=
"message-title"
id=
"messageTitle"
>
Townships Combat League
</div>
<div
class=
"message-content"
id=
"messageContent"
>
Waiting for game to start....
</div>
</div>
</div>
<!-- Bottom info bar -->
<div
class=
"info-bar"
>
<div
class=
"fighter-names"
id=
"fighterNames"
>
Loading fighters...
</div>
<div
class=
"venue-info"
id=
"venueInfo"
>
Loading venue...
</div>
</div>
<!-- Load WebPlayerAPI JavaScript -->
<script
src=
"web_player.js"
></script>
<!-- Qt WebChannel JavaScript will be injected by Qt -->
</body>
</html>
\ No newline at end of file
mbetterclient/qt_player/web_player_assets/web_player.js
0 → 100644
View file @
89aa1260
/**
* Web Player JavaScript API
* Provides communication between Qt WebChannel and the HTML5 video player
*/
// Web Player API
class
WebPlayerAPI
{
constructor
()
{
this
.
videoElement
=
null
;
this
.
overlayData
=
{};
this
.
webChannel
=
null
;
this
.
currentVideoPath
=
null
;
this
.
timerInterval
=
null
;
this
.
currentTime
=
0
;
}
// Initialize the web player
init
()
{
console
.
log
(
'Initializing Web Player API'
);
// Get video element
this
.
videoElement
=
document
.
getElementById
(
'webVideoPlayer'
);
if
(
!
this
.
videoElement
)
{
console
.
error
(
'Video element not found'
);
return
false
;
}
// Set up event listeners
this
.
setupEventListeners
();
// Initialize WebChannel if available
this
.
initWebChannel
();
return
true
;
}
// Set up event listeners
setupEventListeners
()
{
if
(
!
this
.
videoElement
)
return
;
this
.
videoElement
.
addEventListener
(
'play'
,
()
=>
{
console
.
log
(
'Video started playing'
);
this
.
startTimer
();
this
.
sendPlayerStatus
(
'playing'
);
});
this
.
videoElement
.
addEventListener
(
'pause'
,
()
=>
{
console
.
log
(
'Video paused'
);
this
.
stopTimer
();
this
.
sendPlayerStatus
(
'paused'
);
});
this
.
videoElement
.
addEventListener
(
'ended'
,
()
=>
{
console
.
log
(
'Video ended'
);
this
.
stopTimer
();
this
.
sendPlayerStatus
(
'ended'
);
});
this
.
videoElement
.
addEventListener
(
'timeupdate'
,
()
=>
{
this
.
updateTimer
();
});
this
.
videoElement
.
addEventListener
(
'loadedmetadata'
,
()
=>
{
console
.
log
(
'Video metadata loaded'
);
this
.
sendVideoInfo
();
});
}
// Initialize WebChannel
initWebChannel
()
{
if
(
typeof
QWebChannel
!==
'undefined'
)
{
new
QWebChannel
(
qt
.
webChannelTransport
,
(
channel
)
=>
{
console
.
log
(
'WebChannel initialized for web player'
);
this
.
webChannel
=
channel
;
// Connect to overlay object if available
if
(
channel
.
objects
.
overlay
)
{
this
.
overlayChannel
=
channel
.
objects
.
overlay
;
// Connect signals
if
(
this
.
overlayChannel
.
dataUpdated
)
{
this
.
overlayChannel
.
dataUpdated
.
connect
((
data
)
=>
{
this
.
handleOverlayData
(
data
);
});
}
if
(
this
.
overlayChannel
.
playVideo
)
{
this
.
overlayChannel
.
playVideo
.
connect
((
filePath
)
=>
{
this
.
playVideo
(
filePath
);
});
}
if
(
this
.
overlayChannel
.
updateOverlayData
)
{
this
.
overlayChannel
.
updateOverlayData
.
connect
((
data
)
=>
{
this
.
handleOverlayData
(
data
);
});
}
// Get initial data
if
(
this
.
overlayChannel
.
getCurrentData
)
{
this
.
overlayChannel
.
getCurrentData
((
data
)
=>
{
this
.
handleOverlayData
(
data
);
});
}
}
});
}
}
// Handle overlay data from Qt
handleOverlayData
(
data
)
{
console
.
log
(
'Web player received overlay data:'
,
data
);
this
.
overlayData
=
data
||
{};
// Update UI with overlay data
this
.
updateUIFromOverlayData
();
// Check for video play command
if
(
data
&&
data
.
videoPath
)
{
this
.
playVideo
(
data
.
videoPath
);
}
}
// Update UI from overlay data
updateUIFromOverlayData
()
{
// Update title, message, etc. from overlay data
if
(
this
.
overlayData
.
title
)
{
const
titleElement
=
document
.
getElementById
(
'messageTitle'
);
if
(
titleElement
)
{
titleElement
.
textContent
=
this
.
overlayData
.
title
;
}
}
if
(
this
.
overlayData
.
message
)
{
const
messageElement
=
document
.
getElementById
(
'messageContent'
);
if
(
messageElement
)
{
messageElement
.
textContent
=
this
.
overlayData
.
message
;
}
}
// Update fighter names and venue if available
if
(
this
.
overlayData
.
fighterNames
)
{
const
fighterNamesElement
=
document
.
getElementById
(
'fighterNames'
);
if
(
fighterNamesElement
)
{
fighterNamesElement
.
textContent
=
this
.
overlayData
.
fighterNames
;
}
}
if
(
this
.
overlayData
.
venueInfo
)
{
const
venueInfoElement
=
document
.
getElementById
(
'venueInfo'
);
if
(
venueInfoElement
)
{
venueInfoElement
.
textContent
=
this
.
overlayData
.
venueInfo
;
}
}
}
// Play video
playVideo
(
filePath
)
{
if
(
!
this
.
videoElement
)
{
console
.
error
(
'Video element not available'
);
return
;
}
console
.
log
(
'Playing video:'
,
filePath
);
this
.
currentVideoPath
=
filePath
;
// Set video source and load
this
.
videoElement
.
src
=
filePath
;
// Add event listeners for better debugging
this
.
videoElement
.
onloadeddata
=
()
=>
{
console
.
log
(
'Video data loaded, attempting to play...'
);
this
.
_attemptPlayback
();
};
this
.
videoElement
.
oncanplay
=
()
=>
{
console
.
log
(
'Video can play, attempting to play...'
);
this
.
_attemptPlayback
();
};
this
.
videoElement
.
onerror
=
(
e
)
=>
{
console
.
error
(
'Video element error:'
,
e
);
this
.
sendPlayerError
(
'load_failed'
,
'Failed to load video source'
);
};
// Load the video
this
.
videoElement
.
load
();
// Debug video element state
console
.
log
(
'Video element after load:'
,
this
.
videoElement
);
console
.
log
(
'Video readyState:'
,
this
.
videoElement
.
readyState
);
console
.
log
(
'Video networkState:'
,
this
.
videoElement
.
networkState
);
console
.
log
(
'Video error:'
,
this
.
videoElement
.
error
);
}
// Attempt playback with autoplay policy handling
_attemptPlayback
()
{
if
(
!
this
.
videoElement
)
return
;
// Check if video is ready to play
if
(
this
.
videoElement
.
readyState
>=
HTMLMediaElement
.
HAVE_FUTURE_DATA
)
{
console
.
log
(
'Video ready, attempting playback...'
);
// Try to play with autoplay policy handling
this
.
videoElement
.
play
().
then
(()
=>
{
console
.
log
(
'Playback started successfully'
);
}).
catch
(
e
=>
{
console
.
error
(
'Playback failed (likely due to autoplay policy):'
,
e
);
// If autoplay is blocked, show controls and let user interact
this
.
videoElement
.
controls
=
true
;
this
.
videoElement
.
muted
=
true
;
// Muted videos can often autoplay
// Try again with muted
this
.
videoElement
.
play
().
catch
(
e2
=>
{
console
.
error
(
'Muted playback also failed:'
,
e2
);
this
.
sendPlayerError
(
'autoplay_blocked'
,
'Autoplay blocked by browser policy'
);
});
});
}
else
{
console
.
log
(
'Video not ready yet, waiting for more data...'
);
}
}
// Start timer
startTimer
()
{
this
.
stopTimer
();
// Clear any existing timer
this
.
currentTime
=
0
;
this
.
updateTimerDisplay
();
this
.
timerInterval
=
setInterval
(()
=>
{
this
.
currentTime
++
;
this
.
updateTimerDisplay
();
},
1000
);
}
// Stop timer
stopTimer
()
{
if
(
this
.
timerInterval
)
{
clearInterval
(
this
.
timerInterval
);
this
.
timerInterval
=
null
;
}
}
// Update timer display
updateTimerDisplay
()
{
const
timerElement
=
document
.
getElementById
(
'matchTimer'
);
if
(
timerElement
)
{
const
minutes
=
Math
.
floor
(
this
.
currentTime
/
60
);
const
seconds
=
this
.
currentTime
%
60
;
const
timeString
=
`
${
minutes
.
toString
().
padStart
(
2
,
'0'
)}
:
${
seconds
.
toString
().
padStart
(
2
,
'0'
)}
`
;
timerElement
.
textContent
=
timeString
;
}
}
// Update timer based on video position
updateTimer
()
{
if
(
this
.
videoElement
&&
!
isNaN
(
this
.
videoElement
.
duration
)
&&
this
.
videoElement
.
duration
>
0
)
{
this
.
currentTime
=
Math
.
floor
(
this
.
videoElement
.
currentTime
);
this
.
updateTimerDisplay
();
}
}
// Send player status to Qt
sendPlayerStatus
(
status
)
{
if
(
this
.
webChannel
&&
this
.
overlayChannel
&&
this
.
overlayChannel
.
sendPlayerStatus
)
{
this
.
overlayChannel
.
sendPlayerStatus
(
status
);
}
}
// Send video info to Qt
sendVideoInfo
()
{
if
(
this
.
videoElement
&&
this
.
webChannel
&&
this
.
overlayChannel
&&
this
.
overlayChannel
.
sendVideoInfo
)
{
const
videoInfo
=
{
duration
:
this
.
videoElement
.
duration
,
width
:
this
.
videoElement
.
videoWidth
,
height
:
this
.
videoElement
.
videoHeight
,
currentTime
:
this
.
videoElement
.
currentTime
};
this
.
overlayChannel
.
sendVideoInfo
(
videoInfo
);
}
}
// Send player error to Qt
sendPlayerError
(
errorType
,
errorMessage
)
{
if
(
this
.
webChannel
&&
this
.
overlayChannel
&&
this
.
overlayChannel
.
sendPlayerError
)
{
this
.
overlayChannel
.
sendPlayerError
(
errorType
,
errorMessage
);
}
}
// Get current player state
getPlayerState
()
{
if
(
!
this
.
videoElement
)
return
null
;
return
{
playing
:
!
this
.
videoElement
.
paused
,
currentTime
:
this
.
videoElement
.
currentTime
,
duration
:
this
.
videoElement
.
duration
,
volume
:
this
.
videoElement
.
volume
,
muted
:
this
.
videoElement
.
muted
,
videoPath
:
this
.
currentVideoPath
};
}
}
// Initialize the web player when DOM is loaded
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
const
webPlayer
=
new
WebPlayerAPI
();
webPlayer
.
init
();
// Expose to global scope for debugging
window
.
webPlayer
=
webPlayer
;
// Expose functions globally for WebChannel communication
window
.
playVideo
=
function
(
filePath
)
{
console
.
log
(
'Global playVideo called with:'
,
filePath
);
webPlayer
.
playVideo
(
filePath
);
};
window
.
updateOverlayData
=
function
(
data
)
{
console
.
log
(
'Global updateOverlayData called with:'
,
data
);
webPlayer
.
handleOverlayData
(
data
);
};
console
.
log
(
'Web Player API initialized and exposed to window.webPlayer'
);
console
.
log
(
'Global functions playVideo and updateOverlayData exposed for WebChannel'
);
});
\ No newline at end of file
test_simple_video.html
0 → 100644
View file @
89aa1260
<!DOCTYPE html>
<html>
<head>
<title>
Simple Video Test
</title>
<style>
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
}
body
{
font-family
:
'Arial'
,
sans-serif
;
background
:
black
;
overflow
:
hidden
;
width
:
100vw
;
height
:
100vh
;
position
:
relative
;
color
:
white
;
}
/* Video container */
.video-container
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
background
:
black
;
z-index
:
100
;
}
/* Video element */
#testVideo
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
background
:
black
;
visibility
:
visible
!important
;
opacity
:
1
!important
;
display
:
block
!important
;
}
/* Status overlay */
.status
{
position
:
absolute
;
top
:
20px
;
left
:
20px
;
color
:
white
;
font-family
:
Arial
,
sans-serif
;
font-size
:
18px
;
z-index
:
1000
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
padding
:
5px
10px
;
border-radius
:
3px
;
}
</style>
</head>
<body>
<div
class=
"status"
id=
"status"
>
Loading video...
</div>
<div
class=
"video-container"
>
<video
id=
"testVideo"
controls
autoplay
playsinline
muted
>
<source
src=
"file:///home/nextime/mbetterc/assets/INTRO.mp4"
type=
"video/mp4"
>
Your browser does not support the video tag.
</video>
</div>
<script>
// Debug video element
const
video
=
document
.
getElementById
(
'testVideo'
);
const
status
=
document
.
getElementById
(
'status'
);
console
.
log
(
'Video element:'
,
video
);
console
.
log
(
'Video style visibility:'
,
window
.
getComputedStyle
(
video
).
visibility
);
console
.
log
(
'Video style opacity:'
,
window
.
getComputedStyle
(
video
).
opacity
);
console
.
log
(
'Video style display:'
,
window
.
getComputedStyle
(
video
).
display
);
// Force video to be visible
video
.
style
.
visibility
=
'visible'
;
video
.
style
.
opacity
=
'1'
;
video
.
style
.
display
=
'block'
;
video
.
addEventListener
(
'loadedmetadata'
,
function
()
{
status
.
textContent
=
'Video loaded: '
+
video
.
duration
+
' seconds'
;
console
.
log
(
'Video metadata loaded'
);
});
video
.
addEventListener
(
'play'
,
function
()
{
status
.
textContent
=
'Video playing'
;
console
.
log
(
'Video started playing'
);
});
video
.
addEventListener
(
'pause'
,
function
()
{
status
.
textContent
=
'Video paused'
;
console
.
log
(
'Video paused'
);
});
video
.
addEventListener
(
'ended'
,
function
()
{
status
.
textContent
=
'Video ended'
;
console
.
log
(
'Video ended'
);
});
video
.
addEventListener
(
'error'
,
function
(
e
)
{
status
.
textContent
=
'Video error: '
+
e
.
message
;
console
.
error
(
'Video error:'
,
e
);
});
// Try to play the video
video
.
play
().
then
(()
=>
{
console
.
log
(
'Playback started successfully'
);
}).
catch
(
e
=>
{
console
.
error
(
'Playback failed:'
,
e
);
status
.
textContent
=
'Playback failed: '
+
e
.
message
;
// If autoplay is blocked, show controls and let user interact
video
.
controls
=
true
;
video
.
muted
=
true
;
// Try again with muted
video
.
play
().
catch
(
e2
=>
{
console
.
error
(
'Muted playback also failed:'
,
e2
);
});
});
</script>
</body>
</html>
\ No newline at end of file
test_video_playback.py
0 → 100644
View file @
89aa1260
#!/usr/bin/env python3
"""
Test script for video playback functionality in the web player
"""
import
sys
import
time
import
logging
from
pathlib
import
Path
from
PyQt6.QtWidgets
import
QApplication
from
PyQt6.QtCore
import
QUrl
,
QTimer
from
PyQt6.QtWebEngineWidgets
import
QWebEngineView
from
PyQt6.QtWebEngineCore
import
QWebEngineProfile
# Setup logging
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logger
=
logging
.
getLogger
(
__name__
)
def
test_video_playback
():
"""Test video playback in a QWebEngineView"""
try
:
logger
.
info
(
"Testing video playback in QWebEngineView..."
)
# Create QApplication
app
=
QApplication
(
sys
.
argv
)
# Create a simple web view
web_view
=
QWebEngineView
()
web_view
.
setWindowTitle
(
"Video Playback Test"
)
web_view
.
resize
(
800
,
600
)
# Get the path to the sample video
sample_video_path
=
Path
(
__file__
)
.
parent
/
"assets"
/
"INTRO.mp4"
if
not
sample_video_path
.
exists
():
# Try to find any video file
assets_dir
=
Path
(
__file__
)
.
parent
/
"assets"
video_files
=
list
(
assets_dir
.
glob
(
"*.mp4"
))
if
video_files
:
sample_video_path
=
video_files
[
0
]
else
:
logger
.
error
(
"No video files found for testing"
)
return
False
# Create HTML content with video player
video_url
=
QUrl
.
fromLocalFile
(
str
(
sample_video_path
))
.
toString
()
html_content
=
f
"""
<!DOCTYPE html>
<html>
<head>
<title>Video Playback Test</title>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
margin: 0;
padding: 0;
background: black;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
overflow: hidden;
}}
.video-container {{
position: absolute;
top: 0;
left: 0;
width: 100
%
;
height: 100
%
;
background: black;
z-index: 100;
}}
video {{
width: 100
%
;
height: 100
%
;
object-fit: contain;
background: black;
visibility: visible !important;
opacity: 1 !important;
display: block !important;
}}
.status {{
position: absolute;
top: 20px;
left: 20px;
color: white;
font-family: Arial, sans-serif;
font-size: 18px;
z-index: 1000;
background: rgba(0,0,0,0.5);
padding: 5px 10px;
border-radius: 3px;
}}
</style>
</head>
<body>
<div class="status" id="status">Loading video...</div>
<div class="video-container">
<video id="testVideo" controls autoplay playsinline>
<source src="{video_url}" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<script>
// Debug video element
const video = document.getElementById('testVideo');
const status = document.getElementById('status');
console.log('Video element:', video);
console.log('Video style visibility:', window.getComputedStyle(video).visibility);
console.log('Video style opacity:', window.getComputedStyle(video).opacity);
console.log('Video style display:', window.getComputedStyle(video).display);
// Force video to be visible
video.style.visibility = 'visible';
video.style.opacity = '1';
video.style.display = 'block';
video.addEventListener('loadedmetadata', function() {{
status.textContent = 'Video loaded: ' + video.duration + ' seconds';
console.log('Video metadata loaded');
}});
video.addEventListener('play', function() {{
status.textContent = 'Video playing';
console.log('Video started playing');
}});
video.addEventListener('pause', function() {{
status.textContent = 'Video paused';
console.log('Video paused');
}});
video.addEventListener('ended', function() {{
status.textContent = 'Video ended';
console.log('Video ended');
}});
video.addEventListener('error', function(e) {{
status.textContent = 'Video error: ' + e.message;
console.error('Video error:', e);
}});
// Try to play the video
video.play().catch(function(e) {{
status.textContent = 'Playback failed: ' + e.message;
console.error('Playback failed:', e);
}});
</script>
</body>
</html>
"""
# Load the HTML content
web_view
.
setHtml
(
html_content
)
web_view
.
show
()
logger
.
info
(
f
"Testing video playback with: {sample_video_path}"
)
logger
.
info
(
"Web view should appear with video playback..."
)
# Run the application for a short time to test
app
.
exec
()
return
True
except
Exception
as
e
:
logger
.
error
(
f
"Video playback test failed: {e}"
)
import
traceback
logger
.
error
(
f
"Traceback: {traceback.format_exc()}"
)
return
False
if
__name__
==
"__main__"
:
success
=
test_video_playback
()
sys
.
exit
(
0
if
success
else
1
)
\ No newline at end of file
test_web_player.py
0 → 100644
View file @
89aa1260
#!/usr/bin/env python3
"""
Test script for the new web player functionality
"""
import
sys
import
time
import
logging
from
pathlib
import
Path
# Setup logging
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logger
=
logging
.
getLogger
(
__name__
)
def
test_web_player
():
"""Test the web player functionality"""
try
:
# Import the main application
from
main
import
main
from
mbetterclient.config.settings
import
QtConfig
from
mbetterclient.core.message_bus
import
MessageBus
from
mbetterclient.qt_player.player
import
QtVideoPlayer
logger
.
info
(
"Testing web player functionality..."
)
# Test 1: Check if the --overlay-web option is properly recognized
test_args
=
[
'--overlay-web'
,
'--debug'
,
'--no-fullscreen'
,
'--no-screen-cast'
]
logger
.
info
(
f
"Test arguments: {test_args}"
)
# Test 2: Verify that the web player HTML file exists
web_player_html_path
=
Path
(
__file__
)
.
parent
/
"mbetterclient"
/
"qt_player"
/
"web_player_assets"
/
"web_player.html"
if
web_player_html_path
.
exists
():
logger
.
info
(
f
"✓ Web player HTML file found: {web_player_html_path}"
)
else
:
logger
.
error
(
f
"✗ Web player HTML file not found: {web_player_html_path}"
)
return
False
# Test 3: Verify that the web player JS file exists
web_player_js_path
=
Path
(
__file__
)
.
parent
/
"mbetterclient"
/
"qt_player"
/
"web_player_assets"
/
"web_player.js"
if
web_player_js_path
.
exists
():
logger
.
info
(
f
"✓ Web player JS file found: {web_player_js_path}"
)
else
:
logger
.
error
(
f
"✗ Web player JS file not found: {web_player_js_path}"
)
return
False
# Test 4: Test video playback with a sample video
sample_video_path
=
Path
(
__file__
)
.
parent
/
"assets"
/
"INTRO.mp4"
if
sample_video_path
.
exists
():
logger
.
info
(
f
"✓ Sample video found: {sample_video_path}"
)
else
:
logger
.
warning
(
f
"✗ Sample video not found: {sample_video_path}"
)
# Try to find any video file
assets_dir
=
Path
(
__file__
)
.
parent
/
"assets"
video_files
=
list
(
assets_dir
.
glob
(
"*.mp4"
))
if
video_files
:
sample_video_path
=
video_files
[
0
]
logger
.
info
(
f
"✓ Found alternative video: {sample_video_path}"
)
else
:
logger
.
warning
(
"No video files found in assets directory"
)
sample_video_path
=
None
logger
.
info
(
"✓ All web player tests passed!"
)
return
True
except
Exception
as
e
:
logger
.
error
(
f
"Web player test failed: {e}"
)
import
traceback
logger
.
error
(
f
"Traceback: {traceback.format_exc()}"
)
return
False
if
__name__
==
"__main__"
:
success
=
test_web_player
()
sys
.
exit
(
0
if
success
else
1
)
\ No newline at end of file
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