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:
+9
-1
@@ -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
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user