| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- 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)
|