""" 后台任务模块 负责创建和管理定期检查Apple Music状态并推送更新的后台任务 """ import time import asyncio import logging from .apple_music import get_music_status from .lyrics import get_lyrics_data from .websocket_manager import ConnectionManager logger = logging.getLogger(__name__) # 存储后台任务引用 background_tasks = set() async def fetch_and_update_lyrics(status, manager): """异步获取歌词并发送更新""" try: # 异步获取歌词 logger.info(f"正在获取歌词...") lyrics = get_lyrics_data(status) logger.info(f"歌词: {lyrics}") # 获取到歌词后,再发送一个歌词更新消息 if lyrics and isinstance(lyrics, dict): # 确保lyrics是一个非空的字典 lyrics_update = { "type": "lyric_update", # 使用不同的消息类型 "timestamp": time.time(), "lyrics": lyrics } await manager.broadcast(lyrics_update) logger.info("歌词获取完成,已发送更新") except Exception as e: logger.error(f"获取歌词并发送更新时出错: {e}") async def update_music_info(manager: ConnectionManager): """定期检查音乐状态并向所有客户端推送更新""" logger.info("后台任务update_music_info开始运行...") # 记录上一次状态 last_position = -1 last_track_name = None last_lyrics = None last_playback_status = None # 记录上一次的播放状态 # 配置更新间隔 check_interval = 0.05 # 内部检查间隔仍保持较短 # 设定重试次数 consecutive_errors = 0 max_consecutive_errors = 10 while True: try: current_time = time.time() # 获取当前状态 status = get_music_status() if status is None: logger.error("获取音乐状态失败,返回None") await asyncio.sleep(1) continue current_status = status.get("status", "unknown") # 错误状态处理 if current_status == "error": logger.error(f"获取音乐状态返回错误: {status.get('error', 'Unknown error')}") await asyncio.sleep(1) continue # 状态变化处理 if current_status != last_playback_status and last_playback_status is not None: logger.info(f"播放状态变化: {last_playback_status} -> {current_status}") # 确定消息类型 message_type = "status_change" if current_status == "playing": message_type = "playback_resumed" elif current_status == "paused": message_type = "playback_paused" elif current_status == "stopped": message_type = "playback_stopped" elif current_status == "notrunning": message_type = "app_not_running" # 向客户端通知状态变化 status_update = { "type": message_type, "timestamp": current_time, "status": status } await manager.broadcast(status_update) last_playback_status = current_status consecutive_errors = 0 await asyncio.sleep(check_interval) continue # 如果没有状态变化,但是还没有记录过状态,进行初始化 if last_playback_status is None: last_playback_status = current_status # 如果应用未运行或已停止,无需进一步处理 if current_status in ["stopped", "notrunning"]: await asyncio.sleep(check_interval) continue # 保存当前播放状态用于下次比较 last_playback_status = current_status # 如果已停止,无需进一步处理 if current_status in ["stopped", "notrunning"]: await asyncio.sleep(check_interval) continue # 以下是正常播放状态下的处理 position = status.get("position", -1) track_name = status.get("track_name") # 安全检查:如果缺少关键数据,跳过本次循环 if track_name is None: logger.debug("歌曲名称为空,跳过本次更新") await asyncio.sleep(check_interval) continue # 检查是否需要发送更新 track_changed = track_name != last_track_name # 如果歌曲变化,获取新歌词 if track_changed: try: # 立即发送歌曲变化通知(不含歌词) logger.info(f"歌曲变化: {last_track_name} -> {track_name}") # 先发送一个不含歌词的track_change消息 initial_update = { "type": "track_change", "timestamp": current_time, "status": status, "lyrics": None # 不包含歌词 } await manager.broadcast(initial_update) # 更新歌曲名称记录 last_track_name = track_name last_position = position # 重置错误计数 consecutive_errors = 0 # 启动一个独立的异步任务来获取歌词并后续发送更新 # 这样主循环可以继续运行,不会阻塞 asyncio.create_task(fetch_and_update_lyrics(status, manager)) except Exception as e: logger.error(f"处理歌曲变化时出错: {e}") await asyncio.sleep(0.5) # 检查歌词是否需要更新(播放位置变化明显) elif abs(last_position - position) > 0.5: # 如果播放位置变化超过0.5秒 try: lyrics = get_lyrics_data(status) # 只有当歌词内容变化时才发送更新 current_lyric = lyrics.get("current_lyric", "") if lyrics and isinstance(lyrics, dict) else "" current_lyric_time = lyrics.get("current_lyric_time", "") if lyrics and isinstance(lyrics, dict) else "" last_current_lyric_time = last_lyrics.get("current_lyric_time", "") if last_lyrics and isinstance(last_lyrics, dict) else "" if current_lyric_time != last_current_lyric_time: if current_lyric is None: current_lyric = "" logger.debug(f"歌词更新: {current_lyric[:20]}...") combined_data = { "type": "lyric_change", "timestamp": current_time, "status": status, "lyrics": lyrics } await manager.broadcast(combined_data) last_lyrics = lyrics last_position = position # 重置错误计数 consecutive_errors = 0 except Exception as e: logger.error(f"处理歌词更新时出错: {e}") await asyncio.sleep(0.5) # 更新上一次位置 if track_changed or (abs(last_position - position) > 0.5): last_position = position # 等待下一次检查 await asyncio.sleep(check_interval) except Exception as e: logger.error(f"更新音乐信息时出错: {e}") consecutive_errors += 1 if consecutive_errors >= max_consecutive_errors: logger.error(f"连续错误次数超过{max_consecutive_errors},退出后台任务") break await asyncio.sleep(1) # 出错时等待1秒再重试 def start_background_task(manager: ConnectionManager): """启动后台任务并添加到跟踪集合中""" task = asyncio.create_task(update_music_info(manager)) background_tasks.add(task) task.add_done_callback(background_tasks.discard) return task def cancel_all_tasks(): """取消所有后台任务""" for task in background_tasks: task.cancel()