Files
lyroc/backend/modules/background_tasks.py
2025-05-27 14:16:48 +08:00

220 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
后台任务模块
负责创建和管理定期检查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()