Stable: Fullscreen player, intro video, playlist update, overlay controls, clean shutdown, mouse hide, and robust exit fixes

This commit is contained in:
2025-08-25 15:30:37 +03:00
parent 027709618e
commit 1ea2ee584c
16 changed files with 626 additions and 46 deletions

View File

@@ -2,6 +2,7 @@ import os
import json
import tkinter as tk
from PIL import Image, ImageTk
import vlc
CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'main_data', 'app_config.txt')
PLAYLIST_DIR = os.path.join(os.path.dirname(__file__), 'static_data', 'playlist')
@@ -20,30 +21,86 @@ class SimpleTkPlayer:
self.hide_controls()
self.root.bind('<Motion>', self.on_activity)
self.root.bind('<Button-1>', self.on_activity)
self.show_current_media()
self.root.after(100, self.next_media_loop)
self.root.after(100, self.ensure_fullscreen)
self.root.after(200, self.hide_mouse)
self.root.after(300, self.move_mouse_to_corner)
self.root.protocol('WM_DELETE_WINDOW', self.exit_app)
def ensure_fullscreen(self):
self.root.attributes('-fullscreen', True)
self.root.update_idletasks()
def create_controls(self):
self.controls_frame = tk.Frame(self.root, bg='#222')
self.controls_frame.place(relx=0.98, rely=0.98, anchor='se')
self.prev_btn = tk.Button(self.controls_frame, text='⏮ Prev', command=self.prev_media, width=8)
self.prev_btn.grid(row=0, column=0, padx=2)
self.pause_btn = tk.Button(self.controls_frame, text='⏸ Pause', command=self.toggle_pause, width=8)
self.pause_btn.grid(row=0, column=1, padx=2)
self.next_btn = tk.Button(self.controls_frame, text='Next ⏭', command=self.next_media, width=8)
self.next_btn.grid(row=0, column=2, padx=2)
self.settings_btn = tk.Button(self.controls_frame, text='⚙ Settings', command=self.open_settings, width=10)
self.settings_btn.grid(row=0, column=3, padx=2)
self.exit_btn = tk.Button(self.controls_frame, text='⏻ Exit', command=self.exit_app, width=8, fg='red')
self.exit_btn.grid(row=0, column=4, padx=2)
# Create a transparent, borderless top-level window for controls
self.controls_win = tk.Toplevel(self.root)
self.controls_win.overrideredirect(True)
self.controls_win.attributes('-topmost', True)
self.controls_win.attributes('-alpha', 0.92)
self.controls_win.configure(bg='')
# Place the window at the bottom right
def place_controls():
self.controls_win.update_idletasks()
w = self.controls_win.winfo_reqwidth()
h = self.controls_win.winfo_reqheight()
sw = self.root.winfo_screenwidth()
sh = self.root.winfo_screenheight()
x = sw - w - 30
y = sh - h - 30
self.controls_win.geometry(f'+{x}+{y}')
self.controls_frame = tk.Frame(self.controls_win, bg='#222', bd=2, relief='ridge')
self.controls_frame.pack()
btn_style = {
'bg': '#333',
'fg': 'white',
'activebackground': '#555',
'activeforeground': '#00e6e6',
'font': ('Arial', 16, 'bold'),
'bd': 0,
'highlightthickness': 0,
'relief': 'flat',
'cursor': 'hand2',
'padx': 10,
'pady': 6
}
self.prev_btn = tk.Button(self.controls_frame, text='⏮ Prev', command=self.prev_media, **btn_style)
self.prev_btn.grid(row=0, column=0, padx=4)
self.pause_btn = tk.Button(self.controls_frame, text='⏸ Pause', command=self.toggle_pause, **btn_style)
self.pause_btn.grid(row=0, column=1, padx=4)
self.next_btn = tk.Button(self.controls_frame, text='Next ⏭', command=self.next_media, **btn_style)
self.next_btn.grid(row=0, column=2, padx=4)
self.settings_btn = tk.Button(self.controls_frame, text='⚙ Settings', command=self.open_settings, **btn_style)
self.settings_btn.grid(row=0, column=3, padx=4)
self.exit_btn = tk.Button(self.controls_frame, text='⏻ Exit', command=self.exit_app, **btn_style)
self.exit_btn.grid(row=0, column=4, padx=4)
self.exit_btn.config(fg='#ff4d4d')
self.controls_win.withdraw()
self.controls_win.after(200, place_controls)
self.root.bind('<Configure>', lambda e: self.controls_win.after(200, place_controls))
def hide_mouse(self):
self.root.config(cursor='none')
def show_mouse(self):
self.root.config(cursor='arrow')
def move_mouse_to_corner(self):
try:
import pyautogui
sw = self.root.winfo_screenwidth()
sh = self.root.winfo_screenheight()
pyautogui.moveTo(sw-2, sh-2)
except Exception:
pass
def show_controls(self):
self.controls_frame.place(relx=0.98, rely=0.98, anchor='se')
self.controls_frame.lift()
self.controls_win.deiconify()
self.controls_win.lift()
self.show_mouse()
self.schedule_hide_controls()
def hide_controls(self):
self.controls_frame.place_forget()
self.controls_win.withdraw()
self.hide_mouse()
def schedule_hide_controls(self):
if hasattr(self, 'hide_controls_timer') and self.hide_controls_timer:
@@ -76,13 +133,54 @@ class SimpleTkPlayer:
self.root.after_cancel(self.pause_timer)
self.pause_timer = None
def play_intro_video(self):
intro_path = os.path.join(os.path.dirname(__file__), 'main_data', 'intro1.mp4')
if os.path.exists(intro_path):
self.show_video(intro_path, on_end=self.after_intro)
else:
self.after_intro()
def after_intro(self):
self.show_current_media()
self.root.after(100, self.next_media_loop)
def show_video(self, file_path, on_end=None):
if hasattr(self, 'vlc_player') and self.vlc_player:
self.vlc_player.stop()
if not hasattr(self, 'video_canvas'):
self.video_canvas = tk.Canvas(self.root, bg='black', highlightthickness=0)
self.video_canvas.pack(fill=tk.BOTH, expand=True)
self.label.pack_forget()
self.video_canvas.pack(fill=tk.BOTH, expand=True)
self.root.attributes('-fullscreen', True)
self.root.update_idletasks()
self.vlc_instance = vlc.Instance()
self.vlc_player = self.vlc_instance.media_player_new()
self.vlc_player.set_mrl(file_path)
self.vlc_player.set_fullscreen(True)
self.vlc_player.set_xwindow(self.video_canvas.winfo_id())
self.vlc_player.play()
def check_end():
if self.vlc_player.get_state() == vlc.State.Ended:
self.video_canvas.pack_forget()
self.label.pack(fill=tk.BOTH, expand=True)
if on_end:
on_end()
else:
self.root.after(200, check_end)
check_end()
def show_current_media(self):
self.root.attributes('-fullscreen', True)
self.root.update_idletasks()
if not self.playlist:
self.label.config(text="No media available", fg='white', font=('Arial', 32))
return
media = self.playlist[self.current_index]
file_path = os.path.join(MEDIA_DATA_PATH, media['file_name'])
if file_path.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')):
if file_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')):
self.show_video(file_path, on_end=self.next_media)
elif file_path.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')):
try:
img = Image.open(file_path)
# Fit to screen without crop or stretch
@@ -115,7 +213,20 @@ class SimpleTkPlayer:
self.root.after(duration * 1000, self.next_media_loop)
def exit_app(self):
self.root.destroy()
# Signal the update thread to stop if stop_event is present
if hasattr(self, 'stop_event') and self.stop_event:
self.stop_event.set()
if hasattr(self, 'app_running') and self.app_running:
self.app_running[0] = False
try:
if hasattr(self, 'controls_win') and self.controls_win:
self.controls_win.destroy()
except:
pass
try:
self.root.destroy()
except:
pass
def open_settings(self):
if self.paused is not True:
@@ -136,6 +247,9 @@ class SimpleTkPlayer:
settings_win.protocol('WM_DELETE_WINDOW', on_close)
settings_win.bind('<Destroy>', lambda e: self.resume_play() if not settings_win.winfo_exists() else None)
def main_start(self):
self.play_intro_video()
def load_latest_playlist():
files = [f for f in os.listdir(PLAYLIST_DIR) if f.startswith('server_playlist_v') and f.endswith('.json')]
if not files:
@@ -152,6 +266,7 @@ def main():
root.attributes('-fullscreen', True)
playlist = load_latest_playlist()
player = SimpleTkPlayer(root, playlist)
player.main_start()
root.mainloop()
if __name__ == '__main__':