This commit is contained in:
2025-09-25 18:43:27 +02:00
parent 0ee635bd2a
commit f0f1818917
4 changed files with 440 additions and 8 deletions

View File

@@ -14,6 +14,7 @@ import queue
import subprocess
import argparse
import random
import socket
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import vlc
@@ -60,6 +61,11 @@ class VideoPlayer:
# Channel refresh tracking
self.last_channel_refresh = time.time()
# Control socket for external commands
self.control_socket_path = "/tmp/video_player_control.sock"
self.control_socket = None
self.control_thread = None
self.logger.info("Video Player initialized")
def load_config(self) -> Dict:
@@ -143,6 +149,10 @@ class VideoPlayer:
self.vlc_instance = vlc.Instance(all_vlc_options)
self.vlc_player = self.vlc_instance.media_player_new()
# Set up event manager for video end detection
self.vlc_event_manager = self.vlc_player.event_manager()
self.vlc_event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.on_video_end)
# Set fullscreen and always on top
if '--fullscreen' in vlc_options:
self.vlc_player.set_fullscreen(True)
@@ -472,6 +482,116 @@ class VideoPlayer:
self.logger.info("Power toggle - shutting down")
self.running = False
def setup_control_socket(self):
"""Setup Unix socket for external control commands"""
try:
# Remove existing socket file if it exists
if os.path.exists(self.control_socket_path):
os.unlink(self.control_socket_path)
# Create Unix socket
self.control_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.control_socket.bind(self.control_socket_path)
self.control_socket.listen(1)
# Start control thread
self.control_thread = threading.Thread(target=self.handle_control_commands, daemon=True)
self.control_thread.start()
self.logger.info(f"Control socket setup at {self.control_socket_path}")
return True
except Exception as e:
self.logger.error(f"Failed to setup control socket: {e}")
return False
def handle_control_commands(self):
"""Handle control commands from external clients"""
while self.running:
try:
# Accept connection
client_socket, _ = self.control_socket.accept()
# Receive command
data = client_socket.recv(1024).decode('utf-8')
command = json.loads(data)
# Process command
response = self.process_control_command(command)
# Send response
response_data = json.dumps(response).encode('utf-8')
client_socket.send(response_data)
client_socket.close()
except Exception as e:
if self.running: # Only log if we're still supposed to be running
self.logger.error(f"Error handling control command: {e}")
time.sleep(0.1)
def process_control_command(self, command):
"""Process a control command and return response"""
action = command.get("action")
try:
if action == "change_channel":
channel = command.get("channel")
if channel and channel in self.channels:
success = self.play_channel(channel)
if success:
return {
"success": True,
"channel": channel,
"channel_name": self.channels[channel]["name"]
}
else:
return {"success": False, "error": "Failed to play channel"}
else:
return {"success": False, "error": f"Invalid channel: {channel}"}
elif action == "random_channel":
success = self.play_random_video()
if success:
return {
"success": True,
"channel": self.current_channel,
"channel_name": self.channels[self.current_channel]["name"] if self.current_channel else "Unknown"
}
else:
return {"success": False, "error": "Failed to play random channel"}
elif action == "list_channels":
return {
"success": True,
"channels": self.channels
}
elif action == "get_current_channel":
if self.current_channel and self.current_channel in self.channels:
return {
"success": True,
"channel": self.current_channel,
"channel_name": self.channels[self.current_channel]["name"]
}
else:
return {
"success": True,
"channel": None,
"channel_name": None
}
elif action == "stop":
self.logger.info("Stop command received via control socket")
self.running = False
return {"success": True}
else:
return {"success": False, "error": f"Unknown action: {action}"}
except Exception as e:
self.logger.error(f"Error processing control command: {e}")
return {"success": False, "error": str(e)}
def start(self, startup_mode: str = "default", channel_number: int = None, video_index: int = None):
"""Start the video player with specified mode
@@ -509,6 +629,10 @@ class VideoPlayer:
else:
self.logger.info("IR remote control disabled, skipping IR processing thread")
# Setup control socket for external commands
if not self.setup_control_socket():
self.logger.warning("Failed to setup control socket, external commands will not work")
# Play based on startup mode
success = False
if startup_mode == "random":
@@ -566,17 +690,21 @@ class VideoPlayer:
finally:
self.cleanup()
def on_video_end(self, event):
"""Callback for when a video ends - start a random channel"""
self.logger.info("Video ended, starting random channel")
if self.channels:
self.play_random_video()
else:
self.logger.error("No channels available to play after video end")
def ensure_video_playing(self):
"""Ensure a video is always playing"""
if not self.vlc_player or not self.vlc_player.is_playing():
if self.current_channel and self.current_channel in self.channels:
self.logger.info(f"Restarting current channel {self.current_channel}")
self.play_channel(self.current_channel)
elif self.channels:
# Play the first available channel
first_channel = min(self.channels.keys())
self.logger.info(f"No current channel, playing first available channel {first_channel}")
self.play_channel(first_channel)
if self.channels:
# Play a random channel instead of restarting current or first channel
self.logger.info("No video playing, starting random channel")
self.play_random_video()
else:
self.logger.error("No channels available to play")
@@ -600,6 +728,21 @@ class VideoPlayer:
if self.enable_ir:
GPIO.cleanup()
# Cleanup control socket
if self.control_socket:
try:
self.control_socket.close()
except:
pass
# Remove socket file
if os.path.exists(self.control_socket_path):
try:
os.unlink(self.control_socket_path)
except:
pass
self.logger.info("Cleanup complete")
def parse_arguments():