Files
signage-player/src/media_player.py
2025-05-10 21:42:30 +03:00

329 lines
15 KiB
Python

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