Fix infinite recursion when restarting player with empty playlist

- Added empty playlist guard in play_current_media() to return
  early instead of calling restart_playlist()
- Added empty playlist guard in restart_playlist() to return
  early instead of calling play_current_media()
- Wrapped SettingsPopup content in ScrollView so fields
  are not cut off on smaller screens
This commit is contained in:
ske087
2026-07-24 09:53:29 +03:00
parent 3845830a86
commit 362f5096a0
2 changed files with 302 additions and 281 deletions
+9 -1
View File
@@ -1299,7 +1299,11 @@ class SignagePlayer(Widget):
Logger.debug(f"SignagePlayer: Skipping play_current_media - player is paused") Logger.debug(f"SignagePlayer: Skipping play_current_media - player is paused")
return return
if not self.playlist or self.current_index >= len(self.playlist): if not self.playlist:
Logger.warning("SignagePlayer: Cannot play - playlist is empty")
return
if self.current_index >= len(self.playlist):
# End of playlist, restart # End of playlist, restart
self.restart_playlist() self.restart_playlist()
return return
@@ -2068,6 +2072,10 @@ class SignagePlayer(Widget):
def restart_playlist(self): def restart_playlist(self):
"""Restart playlist from beginning""" """Restart playlist from beginning"""
if not self.playlist:
Logger.warning("SignagePlayer: Cannot restart - playlist is empty")
return
Logger.info("SignagePlayer: Restarting playlist") Logger.info("SignagePlayer: Restarting playlist")
# Send restart feedback asynchronously (non-blocking) # Send restart feedback asynchronously (non-blocking)
+293 -280
View File
@@ -352,303 +352,316 @@
# Settings popup content # Settings popup content
<SettingsPopup@Popup>: <SettingsPopup@Popup>:
title: 'Player Settings' title: 'Player Settings'
size_hint: 0.8, 0.8 size_hint: 0.9, 0.85
auto_dismiss: True auto_dismiss: True
BoxLayout: BoxLayout:
orientation: 'vertical' orientation: 'vertical'
padding: dp(20) padding: [dp(15), dp(10)]
spacing: dp(15) spacing: dp(8)
# Server configuration ScrollView:
BoxLayout: BoxLayout:
orientation: 'horizontal' orientation: 'vertical'
size_hint_y: None spacing: dp(8)
height: dp(40) size_hint_y: None
spacing: dp(10) height: self.minimum_height
Label:
text: 'Server IP:'
size_hint_x: 0.3
text_size: self.size
halign: 'left'
valign: 'middle'
TextInput: # Server configuration
id: server_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
write_tab: False spacing: dp(8)
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
Label:
# Server port text: 'Server IP:'
BoxLayout: size_hint_x: 0.3
orientation: 'horizontal' text_size: self.size
size_hint_y: None halign: 'left'
height: dp(40) valign: 'middle'
spacing: dp(10)
TextInput:
Label: id: server_input
text: 'Port:' size_hint_x: 0.7
size_hint_x: 0.3 multiline: False
text_size: self.size font_size: sp(13)
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
TextInput: # Server port
id: port_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
hint_text: '80 or 8080 (leave empty for default)' spacing: dp(8)
input_filter: 'int'
write_tab: False Label:
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None text: 'Port:'
size_hint_x: 0.3
# Screen name text_size: self.size
BoxLayout: halign: 'left'
orientation: 'horizontal' valign: 'middle'
size_hint_y: None
height: dp(40) TextInput:
spacing: dp(10) id: port_input
size_hint_x: 0.7
Label: multiline: False
text: 'Screen Name:' font_size: sp(13)
size_hint_x: 0.3 hint_text: '80 or 8080 (leave empty for default)'
text_size: self.size input_filter: 'int'
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
TextInput: # Screen name
id: screen_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
write_tab: False spacing: dp(8)
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
Label:
# Quickconnect key text: 'Screen Name:'
BoxLayout: size_hint_x: 0.3
orientation: 'horizontal' text_size: self.size
size_hint_y: None halign: 'left'
height: dp(40) valign: 'middle'
spacing: dp(10)
TextInput:
Label: id: screen_input
text: 'Quickconnect:' size_hint_x: 0.7
size_hint_x: 0.3 multiline: False
text_size: self.size font_size: sp(13)
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
TextInput: # Quickconnect key
id: quickconnect_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
write_tab: False spacing: dp(8)
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
Label:
# Orientation text: 'Quickconnect:'
BoxLayout: size_hint_x: 0.3
orientation: 'horizontal' text_size: self.size
size_hint_y: None halign: 'left'
height: dp(40) valign: 'middle'
spacing: dp(10)
TextInput:
Label: id: quickconnect_input
text: 'Orientation:' size_hint_x: 0.7
size_hint_x: 0.3 multiline: False
text_size: self.size font_size: sp(13)
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
TextInput: # Orientation
id: orientation_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
write_tab: False spacing: dp(8)
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
Label:
# Touch text: 'Orientation:'
BoxLayout: size_hint_x: 0.3
orientation: 'horizontal' text_size: self.size
size_hint_y: None halign: 'left'
height: dp(40) valign: 'middle'
spacing: dp(10)
TextInput:
Label: id: orientation_input
text: 'Touch:' size_hint_x: 0.7
size_hint_x: 0.3 multiline: False
text_size: self.size font_size: sp(13)
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
TextInput: # Touch
id: touch_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
write_tab: False spacing: dp(8)
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
Label:
# Resolution text: 'Touch:'
BoxLayout: size_hint_x: 0.3
orientation: 'horizontal' text_size: self.size
size_hint_y: None halign: 'left'
height: dp(40) valign: 'middle'
spacing: dp(10)
TextInput:
Label: id: touch_input
text: 'Max Resolution:' size_hint_x: 0.7
size_hint_x: 0.3 multiline: False
text_size: self.size font_size: sp(13)
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
TextInput: # Resolution
id: resolution_input BoxLayout:
size_hint_x: 0.7 orientation: 'horizontal'
multiline: False size_hint_y: None
font_size: sp(14) height: dp(36)
hint_text: '1920x1080 or auto' spacing: dp(8)
write_tab: False
on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None Label:
text: 'Max Resolution:'
# Edit Feature Enable/Disable size_hint_x: 0.3
BoxLayout: text_size: self.size
orientation: 'horizontal' halign: 'left'
size_hint_y: None valign: 'middle'
height: dp(40)
spacing: dp(10) TextInput:
id: resolution_input
Label: size_hint_x: 0.7
text: 'Enable Edit Feature:' multiline: False
size_hint_x: 0.3 font_size: sp(13)
text_size: self.size hint_text: '1920x1080 or auto'
halign: 'left' write_tab: False
valign: 'middle' on_touch_down: root.on_input_touch(self, args[1]) if self.collide_point(*args[1].pos) else None
CheckBox: # Edit Feature Enable/Disable
id: edit_enabled_checkbox BoxLayout:
size_hint_x: None orientation: 'horizontal'
width: dp(40) size_hint_y: None
active: True height: dp(36)
on_active: root.on_edit_feature_toggle(self.active) spacing: dp(8)
Label: Label:
text: '(Allow editing images on this player)' text: 'Enable Edit:'
size_hint_x: 0.4 size_hint_x: 0.3
font_size: sp(12) text_size: self.size
text_size: self.size halign: 'left'
halign: 'left' valign: 'middle'
valign: 'middle'
color: 0.7, 0.7, 0.7, 1 CheckBox:
id: edit_enabled_checkbox
size_hint_x: None
width: dp(36)
active: True
on_active: root.on_edit_feature_toggle(self.active)
Label:
text: '(Allow editing images)'
size_hint_x: 0.4
font_size: sp(11)
text_size: self.size
halign: 'left'
valign: 'middle'
color: 0.7, 0.7, 0.7, 1
# Separator
Widget:
size_hint_y: None
height: dp(5)
# Reset Buttons Section
Label:
text: 'Reset Options:'
size_hint_y: None
height: dp(26)
text_size: self.size
halign: 'left'
valign: 'middle'
bold: True
font_size: sp(14)
# Reset Buttons Row
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(44)
spacing: dp(8)
Button:
id: reset_auth_btn
text: 'Reset Player Auth'
background_color: 0.8, 0.4, 0.2, 1
font_size: sp(12)
on_press: root.reset_player_auth()
Button:
id: reset_playlist_btn
text: 'Reset Playlist to v0'
background_color: 0.8, 0.4, 0.2, 1
font_size: sp(12)
on_press: root.reset_playlist_version()
Button:
id: restart_player_btn
text: 'Restart Player'
background_color: 0.2, 0.6, 0.8, 1
font_size: sp(12)
on_press: root.restart_player()
# Test Connection Button
Button:
id: test_connection_btn
text: 'Test Server Connection'
size_hint_y: None
height: dp(44)
background_color: 0.2, 0.4, 0.8, 1
font_size: sp(13)
on_press: root.test_connection()
# Connection Status Label
Label:
id: connection_status
text: 'Click button to test connection'
size_hint_y: None
height: dp(32)
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(11)
color: 0.7, 0.7, 0.7, 1
# Separator
Widget:
size_hint_y: None
height: dp(5)
# Status information row
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(26)
spacing: dp(8)
Label:
id: playlist_info
text: 'Playlist: N/A'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(11)
Label:
id: media_count_info
text: 'Media: 0'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(11)
Label:
id: status_info
text: 'Status: Idle'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(11)
Widget: # Action buttons (always visible, outside scroll)
size_hint_y: 0.05
# Reset Buttons Section
Label:
text: 'Reset Options:'
size_hint_y: None
height: dp(30)
text_size: self.size
halign: 'left'
valign: 'middle'
bold: True
font_size: sp(16)
# Reset Buttons Row
BoxLayout: BoxLayout:
orientation: 'horizontal' orientation: 'horizontal'
size_hint_y: None size_hint_y: None
height: dp(50) height: dp(44)
spacing: dp(10) spacing: dp(15)
Button:
id: reset_auth_btn
text: 'Reset Player Auth'
background_color: 0.8, 0.4, 0.2, 1
on_press: root.reset_player_auth()
Button:
id: reset_playlist_btn
text: 'Reset Playlist to v0'
background_color: 0.8, 0.4, 0.2, 1
on_press: root.reset_playlist_version()
Button:
id: restart_player_btn
text: 'Restart Player'
background_color: 0.2, 0.6, 0.8, 1
on_press: root.restart_player()
# 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 row
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(30)
spacing: dp(10)
Label:
id: playlist_info
text: 'Playlist: N/A'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(12)
Label:
id: media_count_info
text: 'Media: 0'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(12)
Label:
id: status_info
text: 'Status: Idle'
text_size: self.size
halign: 'center'
valign: 'middle'
font_size: sp(12)
Widget:
size_hint_y: 0.05
# Action buttons
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(50)
spacing: dp(20)
Button: Button:
text: 'Save & Close' text: 'Save & Close'