from kivy.config import Config Config.set('kivy', 'video', 'ffpyplayer') # Set ffpyplayer as the video provider from kivy.app import App from kivy.uix.screenmanager import ScreenManager, Screen # Import ScreenManager and Screen for managing screens from kivy.clock import Clock # Import Clock for scheduling tasks from kivy.core.window import Window # Import Window for handling window events from kivy.uix.video import Video # Import Video widget for video playback from kivy.uix.image import Image # Import Image widget for displaying images from kivy.logger import Logger # Import Logger for logging messages from kivy.lang import Builder # Import Builder for loading KV files from kivy.animation import Animation # Import Animation for fade effects import os # Import os for file and directory operations import json # Import json for handling JSON data import datetime # Import datetime for timestamping logs # Import functions from python_functions.py from python_functions import load_playlist, download_media_files, clean_unused_files # Load the KV file for UI layout Builder.load_file('kv/media_player.kv') # Path to the configuration file CONFIG_FILE = './Resurse/app_config.txt' class MediaPlayer(Screen): """Main screen for media playback.""" def __init__(self, **kwargs): super(MediaPlayer, self).__init__(**kwargs) self.playlist = [] # Initialize the playlist self.current_index = 0 # Index of the currently playing media self.video_player = self.ids.video_player # Reference to the Video widget self.image_display = self.ids.image_display # Reference to the Image widget self.log_file = os.path.join(os.path.dirname(__file__), 'Resurse', 'log.txt') # Path to the log file self.is_paused = False # Track the state of the play/pause button self.reset_timer = None # Timer to reset the button state after 3 minutes self.image_timer = None # Timer for scheduling the next media for images # Schedule periodic updates to check for playlist updates Clock.schedule_interval(self.check_playlist_updates, 300) # Every 5 minutes # Bind key events to handle fullscreen toggle Window.bind(on_key_down=self.on_key_down) # Start a timer to hide the buttons after 10 seconds self.hide_button_timer = Clock.schedule_once(self.hide_buttons, 10) def on_touch_down(self, touch): """Handle touch events to reset the button visibility.""" self.show_buttons() # Make all buttons visible if hasattr(self, 'hide_button_timer'): Clock.unschedule(self.hide_button_timer) # Cancel the existing hide timer self.hide_button_timer = Clock.schedule_once(self.hide_buttons, 10) # Restart the hide timer return super(MediaPlayer, self).on_touch_down(touch) def hide_buttons(self, *args): """Hide all buttons after inactivity.""" self.ids.settings_button.opacity = 0 # Hide the Home button self.ids.right_arrow_button.opacity = 0 # Hide the Right Arrow button self.ids.play_pause_button.opacity = 0 # Hide the Play/Pause button self.ids.left_arrow_button.opacity = 0 # Hide the Left Arrow button def show_buttons(self): """Show all buttons.""" self.ids.settings_button.opacity = 1 # Show the Home button self.ids.right_arrow_button.opacity = 1 # Show the Right Arrow button self.ids.play_pause_button.opacity = 1 # Show the Play/Pause button self.ids.left_arrow_button.opacity = 1 # Show the Left Arrow button def on_key_down(self, window, key, *args): """Handle key events for toggling fullscreen mode.""" if key == 102: # 'f' key Window.fullscreen = not Window.fullscreen def on_enter(self): """Called when the screen is entered.""" self.playlist = load_playlist() # Load the playlist download_media_files(self.playlist) # Download media files from the playlist clean_unused_files(self.playlist) # Remove unused files from the resource folder self.play_media() # Start playing media self.show_buttons() # Ensure buttons are visible when the screen is entered def log_event(self, file_name, event): """Log the start or stop event of a media file and clean up old logs.""" # Get the current timestamp timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') log_message = f"{timestamp} - {event}: {file_name}\n" # Format the log message # Write the log message to the log file with open(self.log_file, 'a') as log: log.write(log_message) Logger.info(f"Logged event: {log_message.strip()}") # Log the event to the console # Clean up logs older than 24 hours self.cleanup_old_logs() def cleanup_old_logs(self): """Delete log entries older than 24 hours.""" try: # Read all log entries if os.path.exists(self.log_file): with open(self.log_file, 'r') as log: lines = log.readlines() # Get the current time now = datetime.datetime.now() # Filter out log entries older than 24 hours filtered_lines = [] for line in lines: try: # Extract the timestamp from the log entry timestamp_str = line.split(' - ')[0] log_time = datetime.datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S') # Keep the log entry if it's within the last 24 hours if (now - log_time).total_seconds() <= 86400: # 24 hours in seconds filtered_lines.append(line) except (ValueError, IndexError): # If the log entry is malformed, skip it Logger.warning(f"Malformed log entry skipped: {line.strip()}") # Write the filtered log entries back to the log file with open(self.log_file, 'w') as log: log.writelines(filtered_lines) Logger.info("Old log entries cleaned up successfully.") except Exception as e: Logger.error(f"Failed to clean up old logs: {e}") def play_media(self): """Play the current media in the playlist.""" if self.playlist: media = self.playlist[self.current_index] # Get the current media file_name = media.get('file_name', '') # Get the file name file_extension = os.path.splitext(file_name)[1].lower() # Get the file extension duration = media.get('duration', 10) # Get the duration (default: 10 seconds) # Define the base directory for media files base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') file_path = os.path.join(base_dir, file_name) # Full path to the media file Logger.info(f"Playing media: {file_path}") # Log the start of the media self.log_event(file_name, "STARTED") # Determine the type of media and play it if file_extension in ['.mp4', '.avi', '.mov']: self.play_video(file_path) # Play video elif file_extension in ['.jpg', '.jpeg', '.png', '.gif']: self.show_image(file_path, duration) # Show image else: Logger.error(f"Unsupported media type for file: {file_name}") def play_video(self, file_path): """Play a video file without a fade-in effect.""" Logger.info(f"Playing video: {file_path}") if not os.path.exists(file_path): Logger.error(f"Video file not found: {file_path}") return # Set the video source and start playback self.video_player.source = file_path self.video_player.state = 'play' # Start playing the video self.video_player.opacity = 1 # Ensure the video is fully visible self.image_display.opacity = 0 # Hide the image display # Schedule the next media after the video's duration Clock.schedule_once(self.next_media, self.video_player.duration) def show_image(self, file_path, duration): """Display an image with a fade-in effect.""" Logger.info(f"Showing image: {file_path}") if not os.path.exists(file_path): Logger.error(f"Image file not found: {file_path}") return # Set the image source self.image_display.source = file_path self.image_display.opacity = 0 # Start with the image hidden self.image_display.reload() # Reload the image to ensure it updates self.video_player.opacity = 0 # Hide the video player # Create a fade-in animation fade_in = Animation(opacity=1, duration=1) # Fade in over 1 second fade_in.start(self.image_display) # Start the fade-in animation # Schedule the next media after the duration self.image_timer = Clock.schedule_once(self.next_media, duration) def next_media(self, dt=None): """Move to the next media in the playlist.""" Logger.info("Navigating to the next media.") self.current_index = (self.current_index + 1) % len(self.playlist) self.play_media() def previous_media(self): """Go to the previous media in the playlist.""" Logger.info("Navigating to the previous media.") self.current_index = (self.current_index - 1) % len(self.playlist) self.play_media() def toggle_play_pause(self): """Toggle the play/pause button state and update its appearance.""" self.manage_play_pause_state() def manage_play_pause_state(self): """Manage the state of the play/pause button and media playback.""" if self.is_paused: Logger.info("Resuming media playback.") self.video_player.state = 'play' # Resume the image timer if it exists if self.image_timer: Logger.info("Resuming image timer.") self.image_timer() # Update the button to indicate the playing state self.ids.play_pause_button.background_down = './Resurse/play.png' self.ids.play_pause_button.background_normal = './Resurse/play.png' # Cancel the reset timer if it exists if self.reset_timer: Clock.unschedule(self.reset_timer) else: Logger.info("Pausing media playback.") self.video_player.state = 'pause' # Pause the image timer if it exists if self.image_timer: Logger.info("Pausing image timer.") Clock.unschedule(self.image_timer) # Update the button to indicate the paused state self.ids.play_pause_button.background_down = './Resurse/pause.png' self.ids.play_pause_button.background_normal = './Resurse/pause.png' # Start a timer to reset the button state after 3 minutes self.reset_timer = Clock.schedule_once(self.reset_play_pause_state, 180) # Toggle the state self.is_paused = not self.is_paused def reset_play_pause_state(self, dt): """Reset the play/pause button state to 'play' after 3 minutes.""" Logger.info("Resetting play/pause button state to 'play' after timeout.") self.is_paused = False self.video_player.state = 'play' # Resume the image timer if it exists if self.image_timer: Logger.info("Resuming image timer.") self.image_timer() self.ids.play_pause_button.background_down = './Resurse/play.png' self.ids.play_pause_button.background_normal = './Resurse/play.png' def check_playlist_updates(self, dt): """Check for updates to the playlist.""" new_playlist = load_playlist() # Load the new playlist if new_playlist != self.playlist: # Compare the new playlist with the current one Logger.info("Playlist updated. Changes detected.") self.playlist = new_playlist # Update the playlist download_media_files(self.playlist) # Download new media files clean_unused_files(self.playlist) # Remove unused files self.play_media() # Restart media playback else: Logger.info("Playlist update skipped. No changes detected.") class SettingsScreen(Screen): """Settings screen for configuring the app.""" def __init__(self, **kwargs): super(SettingsScreen, self).__init__(**kwargs) self.config_data = self.load_config() # Load the configuration data def load_config(self): """Load the configuration from the config file.""" if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as file: return json.load(file) return { "screen_orientation": "", "screen_name": "", "quickconnect_key": "", "server_ip": "", "port": "" # Default port } def save_config(self): """Save the configuration to the config file.""" self.config_data["screen_orientation"] = self.ids.orientation_input.text self.config_data["screen_name"] = self.ids.screen_name_input.text self.config_data["quickconnect_key"] = self.ids.quickconnect_key_input.text self.config_data["server_ip"] = self.ids.server_ip_input.text self.config_data["port"] = self.ids.port_input.text with open(CONFIG_FILE, 'w') as file: json.dump(self.config_data, file) Logger.info("SettingsScreen: Configuration saved.") # Return to the MediaPlayer screen after saving self.manager.current = 'media_player' def on_pre_enter(self): """Populate input fields with current config data.""" self.ids.orientation_input.text = self.config_data.get("screen_orientation", "landscape") self.ids.screen_name_input.text = self.config_data.get("screen_name", "") self.ids.quickconnect_key_input.text = self.config_data.get("quickconnect_key", "") self.ids.server_ip_input.text = self.config_data.get("server_ip", "") self.ids.port_input.text = self.config_data.get("port", "8080") class MediaPlayerApp(App): """Main application class.""" def build(self): """Build the app and initialize screens.""" Window.fullscreen = True # Start the app in fullscreen mode sm = ScreenManager() # Create a screen manager sm.add_widget(MediaPlayer(name='media_player')) # Add the MediaPlayer screen sm.add_widget(SettingsScreen(name='settings')) # Add the SettingsScreen return sm def open_settings(self): """Switch to the SettingsScreen.""" self.root.current = 'settings' if __name__ == '__main__': MediaPlayerApp().run() # Run the app