update location and setting
BIN
config/resources/arrow.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
config/resources/backward.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
config/resources/exit.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
config/resources/forward.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
config/resources/pause.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
config/resources/play.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
config/resources/settings.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
46
src/main.py
@@ -18,6 +18,7 @@ from kivy.uix.popup import Popup
|
|||||||
from kivy.uix.textinput import TextInput
|
from kivy.uix.textinput import TextInput
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
from kivy.core.window import Window
|
from kivy.core.window import Window
|
||||||
|
from kivy.properties import BooleanProperty
|
||||||
from kivy.logger import Logger
|
from kivy.logger import Logger
|
||||||
from kivy.animation import Animation
|
from kivy.animation import Animation
|
||||||
from kivy.lang import Builder
|
from kivy.lang import Builder
|
||||||
@@ -61,9 +62,20 @@ class SettingsPopup(Popup):
|
|||||||
|
|
||||||
|
|
||||||
class SignagePlayer(Widget):
|
class SignagePlayer(Widget):
|
||||||
|
from kivy.properties import StringProperty
|
||||||
|
resources_path = StringProperty()
|
||||||
|
from kivy.properties import NumericProperty
|
||||||
|
screen_width = NumericProperty(0)
|
||||||
|
screen_height = NumericProperty(0)
|
||||||
|
|
||||||
|
def set_screen_size(self):
|
||||||
|
"""Get the current screen size and set as properties."""
|
||||||
|
self.screen_width, self.screen_height = Window.size
|
||||||
|
Logger.info(f"Screen size detected: {self.screen_width}x{self.screen_height}")
|
||||||
|
|
||||||
|
is_paused = BooleanProperty(False)
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(SignagePlayer, self).__init__(**kwargs)
|
super(SignagePlayer, self).__init__(**kwargs)
|
||||||
|
|
||||||
# Initialize variables
|
# Initialize variables
|
||||||
self.playlist = []
|
self.playlist = []
|
||||||
self.current_index = 0
|
self.current_index = 0
|
||||||
@@ -72,27 +84,33 @@ class SignagePlayer(Widget):
|
|||||||
self.is_paused = False
|
self.is_paused = False
|
||||||
self.config = {}
|
self.config = {}
|
||||||
self.playlist_version = None
|
self.playlist_version = None
|
||||||
|
|
||||||
# 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')
|
||||||
self.media_dir = os.path.join(self.base_dir, 'media')
|
self.media_dir = os.path.join(self.base_dir, 'media')
|
||||||
self.playlists_dir = os.path.join(self.base_dir, 'playlists')
|
self.playlists_dir = os.path.join(self.base_dir, 'playlists')
|
||||||
self.config_file = os.path.join(self.config_dir, 'app_config.json')
|
self.config_file = os.path.join(self.config_dir, 'app_config.json')
|
||||||
|
self.resources_path = os.path.join(self.config_dir, 'resources')
|
||||||
# Create directories if they don't exist
|
# Create directories if they don't exist
|
||||||
for directory in [self.config_dir, self.media_dir, self.playlists_dir]:
|
for directory in [self.config_dir, self.media_dir, self.playlists_dir]:
|
||||||
os.makedirs(directory, exist_ok=True)
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
# Get and set screen size
|
||||||
|
self.set_screen_size()
|
||||||
|
# Bind to window size for fullscreen
|
||||||
|
Window.bind(size=self._update_size)
|
||||||
|
self._update_size(Window, Window.size)
|
||||||
# Initialize player
|
# Initialize player
|
||||||
Clock.schedule_once(self.initialize_player, 0.1)
|
Clock.schedule_once(self.initialize_player, 0.1)
|
||||||
|
|
||||||
# Hide controls timer
|
# Hide controls timer
|
||||||
self.controls_timer = None
|
self.controls_timer = None
|
||||||
|
|
||||||
# Auto-hide controls
|
# Auto-hide controls
|
||||||
self.schedule_hide_controls()
|
self.schedule_hide_controls()
|
||||||
|
|
||||||
|
def _update_size(self, instance, value):
|
||||||
|
self.size = value
|
||||||
|
if hasattr(self, 'ids') and 'content_area' in self.ids:
|
||||||
|
self.ids.content_area.size = value
|
||||||
|
|
||||||
def initialize_player(self, dt):
|
def initialize_player(self, dt):
|
||||||
"""Initialize the player - load config and start playlist checking"""
|
"""Initialize the player - load config and start playlist checking"""
|
||||||
Logger.info("SignagePlayer: Initializing player...")
|
Logger.info("SignagePlayer: Initializing player...")
|
||||||
@@ -311,15 +329,11 @@ class SignagePlayer(Widget):
|
|||||||
source=image_path,
|
source=image_path,
|
||||||
allow_stretch=True,
|
allow_stretch=True,
|
||||||
keep_ratio=False,
|
keep_ratio=False,
|
||||||
size_hint=(1, 1),
|
size_hint=(1, 1)
|
||||||
pos_hint={'x': 0, 'y': 0}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ids.content_area.add_widget(self.current_widget)
|
self.ids.content_area.add_widget(self.current_widget)
|
||||||
|
|
||||||
# Schedule next media after duration
|
# Schedule next media after duration
|
||||||
Clock.schedule_once(self.next_media, duration)
|
Clock.schedule_once(self.next_media, duration)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.error(f"SignagePlayer: Error playing image {image_path}: {e}")
|
Logger.error(f"SignagePlayer: Error playing image {image_path}: {e}")
|
||||||
self.next_media()
|
self.next_media()
|
||||||
@@ -436,17 +450,13 @@ class SignagePlayer(Widget):
|
|||||||
|
|
||||||
class SignagePlayerApp(App):
|
class SignagePlayerApp(App):
|
||||||
def build(self):
|
def build(self):
|
||||||
# Get screen resolution info
|
# Force fullscreen and borderless
|
||||||
|
Window.fullscreen = True
|
||||||
|
Window.borderless = True
|
||||||
Logger.info(f"SignagePlayerApp: Screen size: {Window.size}")
|
Logger.info(f"SignagePlayerApp: Screen size: {Window.size}")
|
||||||
Logger.info(f"SignagePlayerApp: Available screen size: {Window.system_size if hasattr(Window, 'system_size') else 'N/A'}")
|
Logger.info(f"SignagePlayerApp: Available screen size: {Window.system_size if hasattr(Window, 'system_size') else 'N/A'}")
|
||||||
|
|
||||||
# Set window to fullscreen and borderless
|
|
||||||
Window.fullscreen = 'auto'
|
|
||||||
Window.borderless = True
|
|
||||||
|
|
||||||
# Hide cursor after 3 seconds of inactivity
|
# Hide cursor after 3 seconds of inactivity
|
||||||
Clock.schedule_once(self.hide_cursor, 3)
|
Clock.schedule_once(self.hide_cursor, 3)
|
||||||
|
|
||||||
return SignagePlayer()
|
return SignagePlayer()
|
||||||
|
|
||||||
def hide_cursor(self, dt):
|
def hide_cursor(self, dt):
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
#:kivy 2.1.0
|
#:kivy 2.1.0
|
||||||
|
|
||||||
<SignagePlayer>:
|
<SignagePlayer@FloatLayout>:
|
||||||
|
size: root.screen_width, root.screen_height
|
||||||
canvas.before:
|
canvas.before:
|
||||||
Color:
|
Color:
|
||||||
rgba: 0, 0, 0, 1
|
rgba: 0, 0, 0, 1
|
||||||
Rectangle:
|
Rectangle:
|
||||||
size: self.size
|
size: self.size
|
||||||
pos: self.pos
|
pos: self.pos
|
||||||
|
|
||||||
# Main content area - FULLSCREEN WIDGET
|
# Main content area for images/videos
|
||||||
Widget:
|
FloatLayout:
|
||||||
id: content_area
|
id: content_area
|
||||||
size_hint: 1, 1
|
size: root.screen_width, root.screen_height
|
||||||
|
size_hint: None, None
|
||||||
pos_hint: {'x': 0, 'y': 0}
|
pos_hint: {'x': 0, 'y': 0}
|
||||||
|
|
||||||
# Status label overlay (shown when no content or errors)
|
# Status label overlay (centered)
|
||||||
Label:
|
Label:
|
||||||
id: status_label
|
id: status_label
|
||||||
text: 'Loading...'
|
text: 'Loading...'
|
||||||
size_hint: None, None
|
size_hint: 0.5, 0.5
|
||||||
size: dp(400), dp(60)
|
size: dp(600), dp(120)
|
||||||
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
|
||||||
color: 1, 1, 1, 1
|
color: 1, 1, 1, 1
|
||||||
font_size: sp(18)
|
font_size: sp(18)
|
||||||
@@ -33,110 +35,66 @@
|
|||||||
size: self.size
|
size: self.size
|
||||||
pos: self.pos
|
pos: self.pos
|
||||||
radius: [dp(15)]
|
radius: [dp(15)]
|
||||||
|
|
||||||
# Control panel overlay (always on top)
|
# Control panel overlay (bottom center, 5 columns, 1 row)
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
id: controls_layout
|
id: controls_layout
|
||||||
orientation: 'horizontal'
|
orientation: 'horizontal'
|
||||||
size_hint: None, None
|
size_hint: None, None
|
||||||
size: dp(450), dp(60)
|
size: dp(340), dp(70)
|
||||||
pos_hint: {'right': 0.98, 'top': 0.98}
|
pos_hint: {'center_x': 0.5, 'y': 0.02}
|
||||||
opacity: 0
|
opacity: 1
|
||||||
spacing: dp(5)
|
spacing: dp(10)
|
||||||
padding: dp(10)
|
padding: dp(10)
|
||||||
canvas.before:
|
canvas.before:
|
||||||
Color:
|
Color:
|
||||||
rgba: 0.1, 0.1, 0.1, 0.9
|
rgba: 0.1, 0.1, 0.1, 0.9 # 90% transparent
|
||||||
RoundedRectangle:
|
RoundedRectangle:
|
||||||
size: self.size
|
size: self.size
|
||||||
pos: self.pos
|
pos: self.pos
|
||||||
radius: [dp(15)]
|
radius: [dp(20)]
|
||||||
|
|
||||||
# Control buttons
|
|
||||||
Button:
|
Button:
|
||||||
id: prev_btn
|
id: backward_btn
|
||||||
text: '⏮'
|
|
||||||
size_hint: None, None
|
size_hint: None, None
|
||||||
size: dp(60), dp(50)
|
size: dp(60), dp(60)
|
||||||
font_size: sp(18)
|
background_normal: root.resources_path + '/backward.png'
|
||||||
background_color: 0.2, 0.2, 0.2, 0.9
|
background_down: root.resources_path + '/backward.png'
|
||||||
color: 1, 1, 1, 1
|
background_color: 1, 1, 1, 0
|
||||||
on_press: root.previous_media()
|
on_press: root.previous_media()
|
||||||
canvas.before:
|
|
||||||
Color:
|
|
||||||
rgba: self.background_color
|
|
||||||
RoundedRectangle:
|
|
||||||
size: self.size
|
|
||||||
pos: self.pos
|
|
||||||
radius: [dp(8)]
|
|
||||||
|
|
||||||
Button:
|
Button:
|
||||||
id: play_pause_btn
|
id: play_pause_btn
|
||||||
text: '⏸'
|
|
||||||
size_hint: None, None
|
size_hint: None, None
|
||||||
size: dp(60), dp(50)
|
size: dp(60), dp(60)
|
||||||
font_size: sp(18)
|
background_normal: root.resources_path + '/play.png'
|
||||||
background_color: 0.2, 0.6, 0.2, 0.9
|
background_down: root.resources_path + '/pause.png'
|
||||||
color: 1, 1, 1, 1
|
background_color: 1, 1, 1, 0
|
||||||
on_press: root.toggle_pause()
|
on_press: root.toggle_pause()
|
||||||
canvas.before:
|
|
||||||
Color:
|
|
||||||
rgba: self.background_color
|
|
||||||
RoundedRectangle:
|
|
||||||
size: self.size
|
|
||||||
pos: self.pos
|
|
||||||
radius: [dp(8)]
|
|
||||||
|
|
||||||
Button:
|
|
||||||
id: next_btn
|
|
||||||
text: '⏭'
|
|
||||||
size_hint: None, None
|
|
||||||
size: dp(60), dp(50)
|
|
||||||
font_size: sp(18)
|
|
||||||
background_color: 0.2, 0.2, 0.2, 0.9
|
|
||||||
color: 1, 1, 1, 1
|
|
||||||
on_press: root.next_media()
|
|
||||||
canvas.before:
|
|
||||||
Color:
|
|
||||||
rgba: self.background_color
|
|
||||||
RoundedRectangle:
|
|
||||||
size: self.size
|
|
||||||
pos: self.pos
|
|
||||||
radius: [dp(8)]
|
|
||||||
|
|
||||||
Button:
|
Button:
|
||||||
id: settings_btn
|
id: settings_btn
|
||||||
text: '⚙'
|
|
||||||
size_hint: None, None
|
size_hint: None, None
|
||||||
size: dp(60), dp(50)
|
size: dp(60), dp(60)
|
||||||
font_size: sp(18)
|
background_normal: root.resources_path + '/settings.png'
|
||||||
background_color: 0.4, 0.4, 0.2, 0.9
|
background_down: root.resources_path + '/settings.png'
|
||||||
color: 1, 1, 1, 1
|
background_color: 1, 1, 1, 0
|
||||||
on_press: root.show_settings()
|
on_press: root.show_settings()
|
||||||
canvas.before:
|
|
||||||
Color:
|
|
||||||
rgba: self.background_color
|
|
||||||
RoundedRectangle:
|
|
||||||
size: self.size
|
|
||||||
pos: self.pos
|
|
||||||
radius: [dp(8)]
|
|
||||||
|
|
||||||
Button:
|
Button:
|
||||||
id: exit_btn
|
id: exit_btn
|
||||||
text: '⏻'
|
|
||||||
size_hint: None, None
|
size_hint: None, None
|
||||||
size: dp(60), dp(50)
|
size: dp(60), dp(60)
|
||||||
font_size: sp(18)
|
background_normal: root.resources_path + '/exit.png'
|
||||||
background_color: 0.6, 0.2, 0.2, 0.9
|
background_down: root.resources_path + '/exit.png'
|
||||||
color: 1, 1, 1, 1
|
background_color: 1, 1, 1, 0
|
||||||
on_press: root.exit_app()
|
on_press: app.stop()
|
||||||
canvas.before:
|
Button:
|
||||||
Color:
|
id: forward_btn
|
||||||
rgba: self.background_color
|
size_hint: None, None
|
||||||
RoundedRectangle:
|
size: dp(60), dp(60)
|
||||||
size: self.size
|
background_normal: root.resources_path + '/forward.png'
|
||||||
pos: self.pos
|
background_down: root.resources_path + '/forward.png'
|
||||||
radius: [dp(8)]
|
background_color: 1, 1, 1, 0
|
||||||
|
on_press: root.next_media()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Settings popup content
|
# Settings popup content
|
||||||
|
|||||||