updated player with playlist
This commit is contained in:
144
src/main.py
144
src/main.py
@@ -149,6 +149,88 @@ class SettingsPopup(Popup):
|
|||||||
# Restart the control hide timer
|
# Restart the control hide timer
|
||||||
self.player.schedule_hide_controls()
|
self.player.schedule_hide_controls()
|
||||||
|
|
||||||
|
def test_connection(self):
|
||||||
|
"""Test connection to server with current credentials"""
|
||||||
|
# Update status label to show testing
|
||||||
|
self.ids.connection_status.text = 'Testing connection...'
|
||||||
|
self.ids.connection_status.color = (1, 0.7, 0, 1) # Orange
|
||||||
|
|
||||||
|
# Run test in background thread to avoid blocking UI
|
||||||
|
def run_test():
|
||||||
|
try:
|
||||||
|
from player_auth import PlayerAuth
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Get current values from inputs
|
||||||
|
server_ip = self.ids.server_input.text.strip()
|
||||||
|
screen_name = self.ids.screen_input.text.strip()
|
||||||
|
quickconnect = self.ids.quickconnect_input.text.strip()
|
||||||
|
port = self.player.config.get('port', '443')
|
||||||
|
|
||||||
|
if not all([server_ip, screen_name, quickconnect]):
|
||||||
|
Clock.schedule_once(lambda dt: self.update_connection_status(
|
||||||
|
'Error: Fill all fields', False
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build server URL
|
||||||
|
if server_ip.startswith('http://') or server_ip.startswith('https://'):
|
||||||
|
server_url = server_ip
|
||||||
|
if not ':' in server_ip.replace('https://', '').replace('http://', ''):
|
||||||
|
if port and port != '443' and port != '80':
|
||||||
|
server_url = f"{server_ip}:{port}"
|
||||||
|
else:
|
||||||
|
protocol = "https" if port == "443" else "http"
|
||||||
|
server_url = f"{protocol}://{server_ip}:{port}"
|
||||||
|
|
||||||
|
Logger.info(f"SettingsPopup: Testing connection to {server_url}")
|
||||||
|
|
||||||
|
# Create temporary auth instance (don't save)
|
||||||
|
auth = PlayerAuth('/tmp/temp_auth_test.json')
|
||||||
|
|
||||||
|
# Try to authenticate
|
||||||
|
success, error = auth.authenticate(
|
||||||
|
server_url=server_url,
|
||||||
|
hostname=screen_name,
|
||||||
|
quickconnect_code=quickconnect
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clean up temp file
|
||||||
|
try:
|
||||||
|
import os
|
||||||
|
if os.path.exists('/tmp/temp_auth_test.json'):
|
||||||
|
os.remove('/tmp/temp_auth_test.json')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Update UI on main thread
|
||||||
|
if success:
|
||||||
|
player_name = auth.get_player_name()
|
||||||
|
Clock.schedule_once(lambda dt: self.update_connection_status(
|
||||||
|
f'✓ Connected: {player_name}', True
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
Clock.schedule_once(lambda dt: self.update_connection_status(
|
||||||
|
f'✗ Failed: {error}', False
|
||||||
|
))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.error(f"SettingsPopup: Connection test error: {e}")
|
||||||
|
Clock.schedule_once(lambda dt: self.update_connection_status(
|
||||||
|
f'✗ Error: {str(e)}', False
|
||||||
|
))
|
||||||
|
|
||||||
|
# Run in thread
|
||||||
|
threading.Thread(target=run_test, daemon=True).start()
|
||||||
|
|
||||||
|
def update_connection_status(self, message, success):
|
||||||
|
"""Update connection status label"""
|
||||||
|
self.ids.connection_status.text = message
|
||||||
|
if success:
|
||||||
|
self.ids.connection_status.color = (0, 1, 0, 1) # Green
|
||||||
|
else:
|
||||||
|
self.ids.connection_status.color = (1, 0, 0, 1) # Red
|
||||||
|
|
||||||
def save_and_close(self):
|
def save_and_close(self):
|
||||||
"""Save configuration and close popup"""
|
"""Save configuration and close popup"""
|
||||||
# Update config
|
# Update config
|
||||||
@@ -195,6 +277,7 @@ class SignagePlayer(Widget):
|
|||||||
self.playlist_version = None
|
self.playlist_version = None
|
||||||
self.consecutive_errors = 0 # Track consecutive playback errors
|
self.consecutive_errors = 0 # Track consecutive playback errors
|
||||||
self.max_consecutive_errors = 10 # Maximum errors before stopping
|
self.max_consecutive_errors = 10 # Maximum errors before stopping
|
||||||
|
self.intro_played = False # Track if intro has been played
|
||||||
# Paths
|
# Paths
|
||||||
self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
self.base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
self.config_dir = os.path.join(self.base_dir, 'config')
|
self.config_dir = os.path.join(self.base_dir, 'config')
|
||||||
@@ -229,15 +312,15 @@ class SignagePlayer(Widget):
|
|||||||
# Load configuration
|
# Load configuration
|
||||||
self.load_config()
|
self.load_config()
|
||||||
|
|
||||||
|
# Play intro video first
|
||||||
|
self.play_intro_video()
|
||||||
|
|
||||||
# Start async server tasks (non-blocking)
|
# Start async server tasks (non-blocking)
|
||||||
asyncio.ensure_future(self.async_playlist_update_loop())
|
asyncio.ensure_future(self.async_playlist_update_loop())
|
||||||
Logger.info("SignagePlayer: Async server tasks started")
|
Logger.info("SignagePlayer: Async server tasks started")
|
||||||
|
|
||||||
# Start media playback
|
# Start media playback
|
||||||
Clock.schedule_interval(self.check_playlist_and_play, 30) # Check every 30 seconds
|
Clock.schedule_interval(self.check_playlist_and_play, 30) # Check every 30 seconds
|
||||||
|
|
||||||
# Initial playlist check
|
|
||||||
self.check_playlist_and_play(None)
|
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
"""Load configuration from file"""
|
"""Load configuration from file"""
|
||||||
@@ -364,8 +447,63 @@ class SignagePlayer(Widget):
|
|||||||
Logger.error(f"SignagePlayer: Error loading playlist: {e}")
|
Logger.error(f"SignagePlayer: Error loading playlist: {e}")
|
||||||
self.show_error(f"Failed to load playlist: {e}")
|
self.show_error(f"Failed to load playlist: {e}")
|
||||||
|
|
||||||
|
def play_intro_video(self):
|
||||||
|
"""Play intro video on startup"""
|
||||||
|
intro_path = os.path.join(self.resources_path, 'intro1.mp4')
|
||||||
|
|
||||||
|
if not os.path.exists(intro_path):
|
||||||
|
Logger.warning(f"SignagePlayer: Intro video not found at {intro_path}")
|
||||||
|
# Skip intro and load playlist
|
||||||
|
self.intro_played = True
|
||||||
|
self.check_playlist_and_play(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
Logger.info("SignagePlayer: Playing intro video...")
|
||||||
|
self.ids.status_label.opacity = 0 # Hide status label
|
||||||
|
|
||||||
|
# Create video widget for intro
|
||||||
|
intro_video = Video(
|
||||||
|
source=intro_path,
|
||||||
|
state='play',
|
||||||
|
options={'eos': 'stop'},
|
||||||
|
allow_stretch=True,
|
||||||
|
keep_ratio=True,
|
||||||
|
size=self.size,
|
||||||
|
pos=(0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Bind to video end event
|
||||||
|
def on_intro_end(instance, value):
|
||||||
|
if value == 'stop':
|
||||||
|
Logger.info("SignagePlayer: Intro video finished")
|
||||||
|
# Remove intro video
|
||||||
|
if intro_video in self.ids.content_area.children:
|
||||||
|
self.ids.content_area.remove_widget(intro_video)
|
||||||
|
|
||||||
|
# Mark intro as played
|
||||||
|
self.intro_played = True
|
||||||
|
|
||||||
|
# Start normal playlist
|
||||||
|
self.check_playlist_and_play(None)
|
||||||
|
|
||||||
|
intro_video.bind(state=on_intro_end)
|
||||||
|
|
||||||
|
# Add intro video to content area
|
||||||
|
self.ids.content_area.add_widget(intro_video)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
Logger.error(f"SignagePlayer: Error playing intro video: {e}")
|
||||||
|
# Skip intro and load playlist
|
||||||
|
self.intro_played = True
|
||||||
|
self.check_playlist_and_play(None)
|
||||||
|
|
||||||
def check_playlist_and_play(self, dt):
|
def check_playlist_and_play(self, dt):
|
||||||
"""Check for playlist updates and ensure playback is running"""
|
"""Check for playlist updates and ensure playback is running"""
|
||||||
|
# Don't start playlist until intro is done
|
||||||
|
if not self.intro_played:
|
||||||
|
return
|
||||||
|
|
||||||
if not self.playlist:
|
if not self.playlist:
|
||||||
self.load_playlist()
|
self.load_playlist()
|
||||||
|
|
||||||
|
|||||||
@@ -283,7 +283,30 @@
|
|||||||
hint_text: '1920x1080 or auto'
|
hint_text: '1920x1080 or auto'
|
||||||
|
|
||||||
Widget:
|
Widget:
|
||||||
size_hint_y: 0.1
|
size_hint_y: 0.05
|
||||||
|
|
||||||
|
# Test Connection Button
|
||||||
|
Button:
|
||||||
|
id: test_connection_btn
|
||||||
|
text: 'Test Server Connection'
|
||||||
|
size_hint_y: None
|
||||||
|
height: dp(50)
|
||||||
|
background_color: 0.2, 0.4, 0.8, 1
|
||||||
|
on_press: root.test_connection()
|
||||||
|
|
||||||
|
# Connection Status Label
|
||||||
|
Label:
|
||||||
|
id: connection_status
|
||||||
|
text: 'Click button to test connection'
|
||||||
|
size_hint_y: None
|
||||||
|
height: dp(40)
|
||||||
|
text_size: self.size
|
||||||
|
halign: 'center'
|
||||||
|
valign: 'middle'
|
||||||
|
color: 0.7, 0.7, 0.7, 1
|
||||||
|
|
||||||
|
Widget:
|
||||||
|
size_hint_y: 0.05
|
||||||
|
|
||||||
# Status information
|
# Status information
|
||||||
Label:
|
Label:
|
||||||
|
|||||||
Reference in New Issue
Block a user