First adaptation for mobile API client

parent dc928a1f
......@@ -166,6 +166,12 @@ Examples:
action='store_true',
help='Disable PyQt interface (web dashboard only)'
)
parser.add_argument(
'--headless',
action='store_true',
help='Run in headless mode with HLS streaming (alias for --no-qt)'
)
parser.add_argument(
'--no-web',
......@@ -229,6 +235,19 @@ Examples:
help='Enable single window overlay mode (overlay stacks on top of player widget instead of separate window)'
)
parser.add_argument(
'--streamer-port',
type=int,
default=5884,
help='Port for HLS video streaming server (default: 5884)'
)
parser.add_argument(
'--test-stream',
action='store_true',
help='Enable test mode for headless streaming (automatically starts INTRO video)'
)
return parser.parse_args()
def validate_arguments(args):
......@@ -236,11 +255,19 @@ def validate_arguments(args):
if args.no_qt and args.no_web:
print("Error: Cannot disable both Qt and web interfaces")
sys.exit(1)
if args.no_qt and args.headless:
print("Error: Cannot use both --no-qt and --headless (they are aliases)")
sys.exit(1)
if args.web_port < 1 or args.web_port > 65535:
print("Error: Web port must be between 1 and 65535")
sys.exit(1)
if args.streamer_port < 1 or args.streamer_port > 65535:
print("Error: Streamer port must be between 1 and 65535")
sys.exit(1)
# Directory creation is handled by AppSettings.ensure_directories()
# which uses persistent user directories for PyInstaller compatibility
pass
......@@ -269,12 +296,14 @@ def main():
settings.fullscreen = not args.no_fullscreen
settings.web_host = args.web_host
settings.web_port = args.web_port
settings.streamer_port = args.streamer_port
settings.debug_mode = args.debug or args.dev_mode
settings.dev_message = args.dev_message
settings.debug_messages = args.debug_messages
settings.debug_player = args.debug_player
settings.debug_overlay = args.debug_overlay
settings.enable_qt = not args.no_qt
settings.enable_qt = not args.no_qt and not args.headless
settings.enable_headless = args.headless
settings.enable_web = not args.no_web
# Timer settings
......@@ -300,6 +329,13 @@ def main():
# Overlay settings
settings.qt.overlay_single_window = args.overlay_single
# Test streaming settings
# Enable test stream automatically in headless mode
settings.test_stream = args.test_stream or args.headless
# Set headless flag
settings.enable_headless = args.headless
if args.db_path:
settings.database_path = args.db_path
......
......@@ -374,6 +374,7 @@ class AppSettings:
debug_overlay: bool = False # Enable debug mode for overlay rendering
enable_web: bool = True
enable_qt: bool = True
enable_headless: bool = False
enable_api_client: bool = True
enable_screen_cast: bool = True # Enabled by default, can be disabled with --no-screen-cast
......@@ -381,6 +382,7 @@ class AppSettings:
fullscreen: bool = True
web_host: str = "127.0.0.1"
web_port: int = 5001
streamer_port: int = 5884 # HLS streaming port
database_path: Optional[str] = None
def __post_init__(self):
......@@ -421,6 +423,7 @@ class AppSettings:
"debug_overlay": self.debug_overlay,
"enable_web": self.enable_web,
"enable_qt": self.enable_qt,
"enable_headless": self.enable_headless,
"enable_api_client": self.enable_api_client,
"enable_screen_cast": self.enable_screen_cast
}
......@@ -452,7 +455,7 @@ class AppSettings:
settings.timer = TimerConfig(**data["timer"])
# Update app settings
for key in ["version", "debug_mode", "dev_message", "debug_messages", "debug_player", "debug_overlay", "enable_web", "enable_qt", "enable_api_client", "enable_screen_cast"]:
for key in ["version", "debug_mode", "dev_message", "debug_messages", "debug_player", "debug_overlay", "enable_web", "enable_qt", "enable_headless", "enable_api_client", "enable_screen_cast"]:
if key in data:
setattr(settings, key, data[key])
......
......@@ -128,10 +128,15 @@ class MbetterClientApplication:
stored_settings.fullscreen = self.settings.fullscreen
stored_settings.web_host = self.settings.web_host
stored_settings.web_port = self.settings.web_port
stored_settings.streamer_port = getattr(self.settings, 'streamer_port', 5884) # Preserve streamer port
stored_settings.database_path = self.settings.database_path
stored_settings.enable_qt = self.settings.enable_qt
stored_settings.enable_web = self.settings.enable_web
stored_settings.enable_screen_cast = self.settings.enable_screen_cast # Preserve screen cast setting
# Preserve test_stream setting
if hasattr(self.settings, 'test_stream'):
stored_settings.test_stream = self.settings.test_stream
# Preserve command line debug settings
......@@ -147,6 +152,9 @@ class MbetterClientApplication:
stored_settings.web.ssl_key_path = self.settings.web.ssl_key_path
stored_settings.web.ssl_auto_generate = self.settings.web.ssl_auto_generate
# Preserve command line headless setting
stored_settings.enable_headless = self.settings.enable_headless
self.settings = stored_settings
# Restore command-line rustdesk_id after settings merge
......@@ -216,13 +224,19 @@ class MbetterClientApplication:
logger.error("Template watcher initialization failed")
return False
# Initialize PyQt video player
# Initialize PyQt video player or headless RTSP streamer
if self.settings.enable_qt:
if self._initialize_qt_player():
components_initialized += 1
else:
logger.error("Qt player initialization failed")
return False
elif self.settings.enable_headless:
if self._initialize_headless_player():
components_initialized += 1
else:
logger.error("Headless player initialization failed")
return False
# Initialize web dashboard
if self.settings.enable_web:
......@@ -326,24 +340,50 @@ class MbetterClientApplication:
"""Initialize PyQt video player"""
try:
from ..qt_player.player import QtVideoPlayer
self.qt_player = QtVideoPlayer(
message_bus=self.message_bus,
settings=self.settings.qt,
debug_player=self.settings.debug_player,
debug_overlay=self.settings.debug_overlay
)
# Don't register with thread manager since QtPlayer no longer inherits from ThreadedComponent
# Instead, we'll handle it separately in the run method
pass
logger.info("Qt player initialized")
return True
except Exception as e:
logger.error(f"Qt player initialization failed: {e}")
return False
def _initialize_headless_player(self) -> bool:
"""Initialize headless RTSP streamer"""
try:
from ..core.rtsp_streamer import RTSPStreamer
# Check if test mode is enabled (passed from command line)
test_mode = getattr(self.settings, 'test_stream', False)
logger.info(f"Application: test_mode from settings: {test_mode}")
self.headless_player = RTSPStreamer(
message_bus=self.message_bus,
settings=self.settings,
debug_player=self.settings.debug_player,
test_mode=test_mode
)
# Register with thread manager
self.thread_manager.register_component("headless_player", self.headless_player)
logger.info("Headless RTSP streamer initialized")
return True
except Exception as e:
logger.error(f"Headless player initialization failed: {e}")
return False
def _initialize_web_dashboard(self) -> bool:
"""Initialize web dashboard"""
......
#!/usr/bin/env python3
"""
Test script for headless RTSP/HLS streaming
"""
import sys
import time
import os
from pathlib import Path
# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from mbetterclient.core.message_bus import MessageBus, Message, MessageType, MessageBuilder
def test_headless_streaming():
"""Test headless streaming by sending video play messages"""
# Create message bus
message_bus = MessageBus()
# Register test component
message_bus.register_component("test")
print("Starting headless streaming test...")
# Send a video play message for the INTRO video
intro_path = project_root / "assets" / "INTRO.mp4"
if not intro_path.exists():
print(f"INTRO.mp4 not found at {intro_path}")
return
print(f"Playing video: {intro_path}")
# Create video play message
video_play_message = MessageBuilder.video_play(
sender="test",
file_path=str(intro_path),
overlay_data={"title": "Test Stream", "message": "Headless streaming test"}
)
# Publish the message
message_bus.publish(video_play_message, broadcast=True)
print("Video play message sent. Streaming should start...")
# Wait a bit
time.sleep(5)
print("Test completed. Check if streaming is working.")
if __name__ == "__main__":
test_headless_streaming()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment