import json import requests import time as time_module import threading import yaml import os from kodi_util.kodi_module import KodiClient from kodi_util.kodi_play.thread import PlaybackMonitorThread, KodiStateMonitorThread class KodiServer: def __init__(self, config_path="config/config.yaml"): """初始化 KodiServer Args: config_path: 配置文件路径,默认为 config/config.yaml """ self.clients = [] self.state_monitor = None # 读取配置文件 if not os.path.exists(config_path): raise FileNotFoundError(f"配置文件 {config_path} 不存在") with open(config_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) # 从配置创建客户端 if 'kodi_clients' not in config: raise ValueError("配置文件中缺少 kodi_clients 配置") for client_config in config['kodi_clients']: try: # 确保使用正确的参数名 client = KodiClient( host=client_config.get('ip', 'localhost'), # 使用ip字段作为host参数 port=client_config.get('port', 8080), username=client_config.get('username'), password=client_config.get('password') ) print(f"创建客户端: host={client.host}, port={client.port}") # 添加调试信息 self.clients.append(client) except Exception as e: print(f"创建客户端失败 {client_config.get('ip', 'unknown')}: {str(e)}") if not self.clients: print("警告:没有成功创建任何客户端") # 如果状态监控线程不存在,创建并启动它 if self.state_monitor is None: self.state_monitor = KodiStateMonitorThread(self.clients) self.state_monitor.start() else: # 如果已存在,更新客户端列表 self.state_monitor.update_clients(self.clients) def add_client(self, client): """ 添加Kodi客户端 Args: client: KodiClient实例 """ self.clients.append(client) # 如果状态监控线程不存在,创建并启动它 if self.state_monitor is None: self.state_monitor = KodiStateMonitorThread(self.clients) self.state_monitor.start() else: # 如果已存在,更新客户端列表 self.state_monitor.update_clients(self.clients) def remove_client(self, client): """ 移除Kodi客户端 Args: client: KodiClient实例 """ if client in self.clients: self.clients.remove(client) if self.state_monitor: self.state_monitor.update_clients(self.clients) def sync_play_images(self, image_paths, effect="fade", time=5): """同步播放幻灯片 Args: image_paths: 图片路径列表 effect: 过渡效果 time: 每张图片显示时间(秒) Returns: bool: 是否成功启动同步播放 """ if not self.clients: print("错误:没有连接的客户端") return False # 处理单个图片路径的情况 if isinstance(image_paths, str): image_paths = [image_paths] # 创建一个共享的Event来控制所有客户端同时开始播放 start_event = threading.Event() # 用于跟踪成功和失败的客户端 ready_clients = [] failed_clients = [] def play_thread(client): try: # 准备阶段 client.ready_event.clear() print(f"客户端 {client.url} 准备中...") # 先停止当前播放 client.stop_playback() time_module.sleep(0.5) # 给一点时间确保停止完成 # 标记客户端准备好了 client.set_ready() # 等待全局开始信号 start_event.wait() # 播放图片 result = False if len(image_paths) == 1: # 只有一张图片,直接播放 print(f"只有一张图片,客户端 {client.url} 直接播放") result = client.play_image(image_paths[0]) else: # 多张图片使用幻灯片效果 # 设置效果 client.set_slideshow_settings(effect=effect, time=time) # 播放图片 result = client.play_image_loop(image_paths) if result: print(f"客户端 {client.url} 播放成功") ready_clients.append(client) return True else: print(f"客户端 {client.url} 播放失败") failed_clients.append(client) return False except Exception as e: print(f"客户端 {client.url} 播放出错: {str(e)}") failed_clients.append(client) return False # 启动所有播放线程 threads = [] for client in self.clients: thread = threading.Thread(target=play_thread, args=(client,)) thread.daemon = True # 设置为守护线程 threads.append(thread) thread.start() # 等待所有客户端准备就绪 all_ready = True for client in self.clients: if not client.wait_for_ready(timeout=10): print(f"客户端 {client.url} 未能在超时时间内准备就绪") all_ready = False if not all_ready: print("警告:部分客户端未准备就绪,但仍将继续尝试同步播放") # 同时触发所有客户端开始播放 print("所有客户端准备就绪,开始同步播放...") start_event.set() # 等待所有线程完成 for thread in threads: thread.join(timeout=15) # 设置超时时间,避免无限等待 # 报告播放状态 if failed_clients: print(f"警告:{len(failed_clients)} 个客户端播放失败,{len(ready_clients)} 个客户端播放成功") # 只要有一个客户端成功播放,就认为同步播放成功 return len(ready_clients) > 0 def sync_play_video(self, video_path, start_time=0, end_time=None, sound_client_index=0, default_volume=100, loop=False, client_loop_settings=None): """ 同步播放视频 - 并发下发版本:为所有客户端并发创建播放线程 Args: video_path: 视频路径 start_time: 开始时间(秒) end_time: 结束时间(秒) sound_client_index: 播放声音的客户端索引,默认为0(第一台设备) 如果为-1,则所有客户端都播放声音 default_volume: 播放声音的客户端的默认音量(0-100) loop: 是否循环播放视频,如果提供了client_loop_settings则此参数作为默认值 client_loop_settings: 每个客户端的循环播放设置列表,与clients列表长度相同 Returns: dict: 播放结果 """ if not self.clients: return {"success": False, "message": "没有可用的客户端"} # 验证声音客户端索引是否有效 master_client_index = sound_client_index if master_client_index >= len(self.clients) and master_client_index != -1: print(f"警告:指定的声音客户端索引 {master_client_index} 超出范围,将使用默认客户端") master_client_index = 0 # 如果为-1,设置为第一个客户端作为主客户端 if master_client_index == -1 and len(self.clients) > 0: master_client_index = 0 print(f"声音客户端索引为-1,使用第一个客户端(索引0)作为主客户端") # 验证client_loop_settings是否有效 if client_loop_settings is not None: if len(client_loop_settings) != len(self.clients): print(f"警告:循环设置列表长度({len(client_loop_settings)})与客户端数量({len(self.clients)})不匹配,将使用默认设置") client_loop_settings = None # 创建同步事件 start_event = threading.Event() # 用于跟踪成功和失败的客户端 success_clients = [] failed_clients = [] def play_thread(client, idx): """每个客户端的播放线程""" try: # 准备阶段 client.ready_event.clear() print(f"客户端 {client.host} 准备中...") # 先停止当前播放 client.stop_playback() time_module.sleep(0.2) # 短暂等待确保停止完成 # 设置音量 if idx == master_client_index or sound_client_index == -1: print(f"客户端 {client.host} 将播放声音,音量设置为 {default_volume}%") client.set_volume(default_volume) else: print(f"客户端 {client.host} 将静音播放") client.set_volume(0) # 获取此客户端的循环设置 client_loop = loop # 默认使用传入的loop参数 if client_loop_settings is not None and idx < len(client_loop_settings): client_loop = client_loop_settings[idx] # 标记客户端准备好了 client.set_ready() # 等待全局开始信号 start_event.wait() # 播放视频 print(f"客户端 {client.host} 开始播放,循环设置: {client_loop}") result = client.play_video(video_path, loop=client_loop) if result and result.get('result') == 'OK': print(f"客户端 {client.host} 播放成功") success_clients.append(client) return True else: print(f"客户端 {client.host} 播放失败: {result}") failed_clients.append(client) return False except Exception as e: print(f"客户端 {client.host} 播放出错: {str(e)}") failed_clients.append(client) return False # 启动所有播放线程 threads = [] for idx, client in enumerate(self.clients): thread = threading.Thread(target=play_thread, args=(client, idx)) thread.daemon = True # 设置为守护线程 threads.append(thread) thread.start() # 等待所有客户端准备就绪 all_ready = True for client in self.clients: if not client.wait_for_ready(timeout=10): print(f"客户端 {client.host} 未能在超时时间内准备就绪") all_ready = False if not all_ready: print("警告:部分客户端未准备就绪,但仍将继续尝试同步播放") # 同时触发所有客户端开始播放 print("所有客户端准备就绪,开始同步播放...") start_event.set() # 等待所有线程完成 for thread in threads: thread.join(timeout=15) # 设置超时时间,避免无限等待 # 返回播放结果 if not success_clients: return { "success": False, "message": "所有客户端播放失败", "failed_clients": [c.host for c in failed_clients] } return { "success": True, "success_clients": [c.host for c in success_clients], "failed_clients": [c.host for c in failed_clients] } def stop_playback(self): """ 停止所有客户端的播放 Returns: dict: 停止结果 """ if not self.clients: return {"success": False, "message": "没有可用的客户端"} success_clients = [] failed_clients = [] for client in self.clients: try: client.stop_playback() success_clients.append(client) except Exception as e: print(f"停止客户端 {client.host} 播放时出错: {str(e)}") failed_clients.append(client) return { "success": len(success_clients) > 0, "success_clients": [c.host for c in success_clients], "failed_clients": [c.host for c in failed_clients] } def get_client_states(self): """ 获取所有客户端的状态 Returns: dict: 客户端状态字典 """ if not self.state_monitor: return {} return self.state_monitor.get_all_states() def get_client_state(self, client_index): """ 获取指定客户端的状态 Args: client_index: 客户端索引 Returns: dict: 客户端状态 """ if not self.state_monitor: return {} return self.state_monitor.get_state(client_index) def is_client_playing(self, client_index): """ 检查指定客户端是否正在播放 Args: client_index: 客户端索引 Returns: bool: 是否正在播放 """ if not self.state_monitor: return False return self.state_monitor.is_client_playing(client_index) def get_client_playback_time(self, client_index): """ 获取指定客户端的播放时间 Args: client_index: 客户端索引 Returns: float: 播放时间(秒) """ if not self.state_monitor: return 0.0 return self.state_monitor.get_playback_time(client_index) def get_client_volume(self, client_index): """ 获取指定客户端的音量 Args: client_index: 客户端索引 Returns: int: 音量值 """ if not self.state_monitor: return 0 return self.state_monitor.get_volume(client_index)